Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What causes this constructor to delegate to itself when it takes an initializer list and delegates a vector?

Tags:

c++

c++11

c++14

#include <initializer_list>
#include <vector>

struct test
{
    using t = std::vector<test>;
    test(t const &v)
    {
    }
    test(t &&v)
    {
    }
    test(std::initializer_list<test> v)
    : test{t{v}} //error
    {
    }
};

Both Clang and GCC complain that the third constructor, the one taking the initializer list, delegates to itself. I don't understand how this is possible though, because you can't construct an initializer list from a vector.

It is trivial to fix the error by replacing the outer curly braces with round parenthesis, but why would this be an issue in the first place? This almost identical program compiles just fine:

#include <initializer_list>

struct a {};
struct b {};

struct test
{
    test(a const &)
    {
    }
    test(a &&)
    {
    }
    test(std::initializer_list<b> v)
    : test{a{}} //no error, still using curly braces
    {
    }
};

Interestingly, with the above second example, the error reappears if you substitute b with test. Can someone explain what is going on here?

like image 725
LB-- Avatar asked Apr 21 '15 05:04

LB--


1 Answers

The type of t{v} is std::vector<test>. The idea is that init-list constructors are always preferred wrt any other constructors, so test{t{v}} will first try to call an init-list constructor, if one exists, and if the types are compatible. In your case, this is possible, since test itself can be implicitly constructed from a std::vector<test> (via your first 2 constructors), so the compiler ends up delegating to the init-list constructor, hence the error.

In the second case, there is no ambiguity, since the type a{}is not implicitly convertible anymore to std::initializer_list<b>.

Make the constructors explicit in the first example, or call the base constructor with test(t{v}) instead, and your ambiguity will disappear (the compiler won't perform the implicit conversion anymore).

A simpler example (live here) that exhibits essentially the same behaviour as your first example is:

#include <initializer_list>

struct test
{
    /*explicit*/ test(int){} // uncomment explicit and no more errors
    test( std::initializer_list<test> v)
        : test{42} {} // error, implicitly converts 42 to test(42) via the test(int)
};

int main(){}

The relevant part of the standard that deals with init-list constructors is §13.3.1.7/1 [over.match.list] - citation below taken from a now-deleted answer of @Praetorian -

When objects of non-aggregate class type T are list-initialized such that 8.5.4 specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:
Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
— If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

like image 112
vsoftco Avatar answered Nov 10 '22 19:11

vsoftco