Home > OS >  initializer_list constructor somehow excluded from std::variant constructor overload set
initializer_list constructor somehow excluded from std::variant constructor overload set

Time:07-20

Help me solve this puzzle: In the following code I have an std::variant which forward declares a struct proxy which derives from this variant. This struct is only used because recursive using declarations are afaik not a thing in C (unfortunately). Anyway, I pull in all the base class constructors of the variant which define for each declared variant alternative T

template< class T >
constexpr variant( T&& t ) noexcept(/* see below */);

according to cppreference. I would assume that this means that a constructor for std::initializer_list<struct proxy> as type T is also defined. However, this doesn't seem to be the case. The following code results in an error:

#include <variant>

using val = std::variant<std::monostate, int, double, std::initializer_list<struct proxy>>;

struct proxy : val
{
    using val::variant;
};

int main()
{
    proxy some_obj = {1,2,3,2.5,{1,2}};
}

CompilerExplorer

Clang Error (because gcc doesn't go into much detail):

<source>:12:11: error: no matching constructor for initialization of 'proxy'
    proxy some_obj = {1,2,3,2.5,{1,2}};
          ^          ~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.0/../../../../include/c  /13.0.0/variant:1434:2: note: candidate template ignored: could not match 'in_place_type_t<_Tp>' against 'int'
        variant(in_place_type_t<_Tp>, initializer_list<_Up> __il,
        ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.0/../../../../include/c  /13.0.0/variant:1455:2: note: candidate template ignored: could not match 'in_place_index_t<_Np>' against 'int'
        variant(in_place_index_t<_Np>, initializer_list<_Up> __il,
        ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.0/../../../../include/c  /13.0.0/variant:1424:2: note: candidate template ignored: could not match 'in_place_type_t<_Tp>' against 'int'
        variant(in_place_type_t<_Tp>, _Args&&... __args)
        ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.0/../../../../include/c  /13.0.0/variant:1444:2: note: candidate template ignored: could not match 'in_place_index_t<_Np>' against 'int'
        variant(in_place_index_t<_Np>, _Args&&... __args)
        ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/13.0.0/../../../../include/c  /13.0.0/variant:1401:7: note: candidate inherited constructor not viable: requires single argument '__rhs', but 5 arguments were provided
      variant(const variant& __rhs) = default;

What I get from this is that the above mentioned constructor taking the variant alternatives T is not considered. Why?

CodePudding user response:

Your proxy class does not have a declared constructor that accepts a std::initializer_list<proxy>. What it has, is a constructor template that accepts any type, T. But for that template to be chosen, the compiler has to deduce the type of T. The braced-init-list {1,2,3,2.5,{1,2}} does not have any inherent type though, so the compiler can't deduce the type T.

It's easy to think that a braced-init-list is a std::initializer_list, but that is not the case. There are some special cases where a std::initializer_list will be implicitly constructed from a braced-init-list, but deducing a template type parameter is not one of those cases.

You could explicitly construct a std::initializer_list<proxy>, i.e.

proxy some_obj = std::initializer_list<proxy>{1,2,3,2.5,std::initializer_list<proxy>{1,2}};

But keep in mind that std::initializer_list only holds pointers to its elements, and it does not extend their lifetimes. All of those proxy objects will go out of scope at the end of the full expression, and some_obj will immediately be holding a std::initializer_list full of dangling pointers. If you need a recursive type, you will almost certainly need to dynamically allocate the recursive children (and remember to clean them up, as well). std::initializer_list is not sufficient for this use case.

  • Related