C++ Logo

std-proposals

Advanced search

Re: [std-proposals] [DRAFT PAPER] std::variant with std::specify_base

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Wed, 14 Sep 2022 19:07:27 -0400
On Wed, Sep 14, 2022 at 5:57 PM Frederick Virchanza Gotham via
Std-Proposals <std-proposals_at_[hidden]> wrote:
>
> On Wed, Sep 14, 2022 at 8:15 PM Lénárd Szolnoki via Std-Proposals
> <std-proposals_at_[hidden]> wrote:
>
> > There is no reason that variant<Cat, Dog> and variant<specify_base<Animal>, Cat, Dog> should be different types.
>
>
> The former doesn't allow the variant object to have no object. The
> second one is very similar to:
>
> variant< monostate, Cat, Dog >
>
> In fact, it might make sense to define "specify_base" as follows:
>
> struct specify_base : monostate { . . . };
>
> or perhaps even modify "monostate" so that instead of being simply:
>
> struct monostate { };
>
> It would be:
>
> template<typename T = void>
> struct monostate { typedef T *base_ptr; };
>
> and then specify_base could simply be:
>
> template<typename T>
> using specify_base = monostate<T>;
>
> Just running through the options here, there's a dozen different ways
> of doing this.
>
>
> > At most this should be a facade over variant, with access to the underlying variant, when needed. But I prefer a convenience function over a variant even more.
>
>
> I want it to be inside "std::variant" so that it can be used with
> template functions and template classes like the following:
>
> template<typename... Types>
> void Print_Variant_Info(std::variant<Types...> const &v)
> {
> std::cout << "Currently holding " << v.index() << std::endl;
> }
>
>
> > As for the implementation I believe there could be potential cv-correctness issues if varant holds cv-qualified alternatives.
> > I believe variant<const Cat, Dog> is valid, you should not be able to get a non-const Animal pointer out of this.
>
>
> I would use static_assert's to prevent the following:
>
> variant< specify_base<Animal>, Dog const, Cat >
>
> But to allow the following:
>
> variant< specify_base<Animal const>, Dog const, Cat >
>
>
> > Finally this whole thing feels a bit wrong for me.
> > This is a weird mixture of two kinds of dynamic polymorphism when one would be enough.
>
>
> Not sure what you mean here by "two kinds of dynamic polymorphism".

Dynamic polymorphism means that you have runtime code which takes a
pointer/reference to a type, but that type actually could be one of a
set of types, or perhaps any type implementing an interface.

Base class polymorphism defines the type the code takes as the base
class, where the actual set of types are the derived classes. But
`variant` also serves as a form of polymorphism:

```
variant<int, float> add_five(variant<int, float> &v)
{
  return std::visit<std::variant<int, float>>(v, [](auto &value)
{return value + 5;});
}
```

This function takes a single type (`variant<int, float>` which
represents one of a set of types (`int` and `float`). And it performs
an operation on them, returning another `variant` of the same type.

You are using base class polymorphism *and* variant-based
polymorphism. Why do you want to use both? Do you have some existing
interface which uses base class polymorphism, but in some other part
of the code, you want to use variant polymorphism on the same object?

> If
> you don't want use the 'new' operator or 'malloc', then you have three
> choices:
>
> (1) std::aligned_union (which is now deprecated)
> (2) std::variant
> (3) alignas(T) char buf[sizeof(T)]; T *const p = std::launder(buf +
> 0u); ::new(p) T; p->~T();
>
>
> > In the end you pay for both kinds of dynamic dispatch, although hopefully the compiler can optimise the virtual call out.
> > Even then, why pay both for a type discriminator plus a vtable pointer when you (hopefully) don't even use the vtable pointer?
>
>
> If we don't know until runtime whether it will be a "JPEG_File" or a
> "Bitmap_File" then of course the vtable pointer is used.

No, it is not:

```
struct JPEG_File //No base class.
{
...
std::byte* jpeg_data() const;
};

struct Bitmap_File //No base class
{
...
std::byte* bitmap_data() const;
};

struct data_visitor
{
  std::byte* operator()(JPEG_File const& file) const
  {
    return file.jpeg_data(); //Not a virtual call
  }

  std::byte* operator(Bitmap_File const& file) const
  {
    return file.bitmap_data(); //Not a virtual call.
  }
};

std::byte* get_data(variant<JPEG_File, Bitmap_File> const &v)
{
  return std::visit(v, data_visitor())
}
```

There are no virtual calls, or base classes, in this example. And at
runtime, the parameter to `get_data` could be either a `JPEG_File` or
a `Bitmap_File`.

Received on 2022-09-14 23:08:21