CPPL
CPPL

Reputation: 816

Understanding how compiler uses plain {}-list inside initialization

Consider the following toy code:

class Y
{
public:
    Y(int, int) { cout << "Y ctor\n"; }
};

class X
{
public:
    //X(initializer_list<int>) { cout << "init\n"; }      // 1
    X(Y&&) { cout << "Y&&\n"; }                         // 2
    X(int, int) { cout << "param\n"; }                  // 3
    X(const X&) { cout << "X&\n"; }                     // 4
    X(const Y&) { cout << "Y&\n"; }                     // 5
    X(Y) { cout << "Y\n"; }                             // 6
    X(X&&) { cout << "X&&\n"; }                         // 7
};

void f(const X&) {  }
void f(const Y&) {  }
void f(Y) {  }

int main()
{
    X x({1,2});
    //f({ 1,2 });                                         // 8
}

I'm trying to understand how the compiler uses {1,2} in X x({1,2}). Followings are my understandings:

May I ask if my understandings are correct? (I highly doubt they are accurate)


I find that it is quite tedious to read out what constructor is invoked by things like X x({1,2});, so I guess in real code we should try to avoid such shorthand and write code in a more explicit way...


On the other hand, if we use the same settings for class X and Y as above, define and call a function g as follows:

X g()
{
    return { 1,2 };
}

int main()
{
    g();
}

The results always seem to be equivalent as initializing the returned temporary by X{1,2} (i.e. call X(initializer_list<int>) when it exists, and in all the other cases call X(int, int)). May I ask if this is true?

Upvotes: 2

Views: 97

Answers (1)

Davis Herring
Davis Herring

Reputation: 39758

  1. X x({1,2}) can’t be an “explicit call for” a std::initializer_list: a braced-init-list has no type. However, there is a rule saying that one is a better match for a specialization of that template than for any other class type.
  2. X(Y&&) is used because X(const X&) isn’t viable. It would be considered a second user-defined conversion even though {…}YX isn’t (because of the two layers of initialization).
  3. Yes, Q and Q&& are often ambiguous like that. Consider that C++17 prvalues don’t even move to initialize either.
  4. Presumably you meant you commented out lines 2 and 6. In that case, the constructors from (references to) X are disqualified as above, and X(const Y&) is a worse match because the const must be added to the non-const list-initialized prvalue. (All user-defined conversions are otherwise indistinguishable.)
  5. f(Y) is better than the other two regardless of X’s constructors for the same const reason.

As for g, yes copy-list-initialization is much the same as direct-list-initialization in the absence of explicit constructors. Since it is list-initialization, rather than direct initialization from an “argument” that is a braced-init-list, it does prefer an initializer-list constructor over all others.

Upvotes: 1

Related Questions