4
#include <initializer_list>
#include <iostream>
using namespace std;

struct Y {};

struct X
{
    X(initializer_list<Y>) { cout << "yay" << endl; }
    explicit X() { cout << "boo" << endl; }
};

X f()
{
    return {};
}

int main()
{
    f();

    return 0;
}

This prints out "boo". Why doesn't it print out "yay" ?

Is there anyway to differentiate the following two constructions:

  1. X()
  2. X{}

or

  1. return X();
  2. return {};

or

void g(const X&)
  1. g(X())
  2. g({})

Thanks.

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • 3
    It makes no sense to use `explicit` with a default ctor, it is only used for ctors that may be called with exactly one argument, to forbid implicit type conversion. For example, `MyObject x = 9;` is only possible if `MyObject` has a non-`explicit` ctor `MyObject(int)`. In your example, `explicit` is simply ignored by the compiler. – Ferdinand Beyer Jan 26 '12 at 15:39
  • yes, I thought (incorrectly) that it might help make this overload go the way I want. – Andrew Tomazos Jan 26 '12 at 15:49

4 Answers4

5

Is there anyway to differentiate the following two constructions:

No. They are not different constructs.

The primary purpose of the {} constructor syntax was to introduce uniform initialization, to have initialization work the same everywhere. If there was a difference between them, it wouldn't be uniform.

If you want to use the empty initializer list constructor, you have to state that you're passing an initializer list explicitly. Like this: return initializer_list<Y>{}; Of course, one of the other purposes of uniform initialization is to not have to type out typenames so much, so you can achieve the same effect with return {{}};

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
5

return {}; will always use a default constructor if there is one.

return X({}); or return {{}}; will construct from an empty initialiser list.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
1

It uses the default constructor because list initialization with {} is meant to be a short form of value initialization always, disregarding of other constructors, even if they are initializer list constructors.

Is there anyway to differentiate the following two constructions: ...

The X() is always value initialization, while X{} is only value initialization if X has a default constructor. If it is an aggregate, then X{} is aggregate initialization (initializing the members of X by a {} recursively). If it only has initializer list constructors and no default constructors, then X() is invalid and X{} could be valid

struct A { A(initializer_list<int>); };
A a = A{}; // valid
A b = A(); // invalid

Essentially, what X{} does depends on what X is. But X() always value initializes.

... or return X(); vs return {};

Something subtle to mention... In return {} the target is copy-list-initialized, while in return X(); first direct-initializes an X. But even though it is copy-list-initialized, it can use an explicit default constructor, because value initialization doesn't care about explicit. However when you do return {} and you try to use an explicit non-default constructor you will error out

struct A {
  explicit A(initializer_list<int>);
};

A f() { return {}; } // error!

struct B { 
  explicit B();
};

B g() { return {}; } // OK
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
0

You could be a bit more explicit:

return initializer_list<Y>();
Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274