I've just learned that we can use the standard library type initializer_list<T>
to achieve initializing a class instance with a {}
-delimited list of elements of type T
. E.g.
class X
{
public:
X(initializer_list<int> lst)
{
cout << "initializer-list constructor\n";
}
X(int i, int j, int k)
{
cout << "constructor taking 3 ints\n";
}
};
int main()
{
X a{ 1,2,3 }; // initializer-list constructor
}
To my knowledge, when the compiler sees { 1,2,3 }
in the above code, it will first seek for a constructor that takes a initializer_list<int>
, and since there is one, it will construct a initializer_list<int>
object out of { 1,2,3 }
. Had there been no constructor which takes a initializer_list<int>
, the compiler will generate code the same as for X a(1,2,3)
.
If the process I described above is correct, to me there is some "magic" going on here: I don't understand why defining a type (initializer_list
) and let a function (the initializer-list constructor) accept an object of that type (say, lst
as above) can "change" the way the function is called. By that I mean: the compiler can now understand {}
notation so that we need not to have things like X a({1,2,3})
, and in such cases prefers the initializer-list constructor to other constructors while treating {}
notation as ()
notation when the initializer-list constructor is absent.
More to the point, can I define a type (say, my own initializer_list
) such that when the compiler sees a special form of instantiation of a class (say, a {}
initializer list), it will search for a constructor in that class that takes an object of my own initializer_list
type, and will treat that special form of instantiation in some other way if such constructor does not exist?
In other words:
class X
{
public:
X(initializer_list<int> lst)
{
cout << "std\n";
}
X(my_initializer_list<int> lst)
{
cout << "user-defined\n";
}
};
int main()
{
X b{ 1,2,3 };
}
Can I implement a my_initializer_list
in the global scope so that the X b{ 1,2,3 };
calls X::X(my_initializer_list<int> lst)
?
My current guess is, it is a feature of the language itself that, when the compiler sees a {}
initializer list, the compiler will always first seek for the constructor that takes a std::initializer_list
, rather than a initializer_list
defined in any other namespace (by that I mean, such behavior of the compiler is not implemented in the std::initializer_list
, but is a feature designed into the compiler in the first place). May I ask if my guess is on the right track?
CodePudding user response:
There is no magic. This is a type of list of initialization, the rules for which are available here: https://en.cppreference.com/w/cpp/language/list_initialization.
Can I implement a my_initializer_list in the global scope so that the X b{ 1,2,3 }; calls X::X(my_initializer_list lst)?
No, this won't do it. Read the above rules - there's a special handling for initializer-list. If there isn't a constructor taking in an intializer-list, the constructor which matches the argument list is picked.
To call X::X(my_initializer_list<int> lst)
you'll have to do X b{my_initializer_list{1,2,3 }};
CodePudding user response:
How the braces {}
are interpreted when creating an instance of a class is a language feature. That is, we cannot change it so that the next time they are encountered, our custom my_initializer_list
contstructor should be selected. What we can control is what happens once it is selected(assuming that it does gets selected by the language rules).
Can I implement a
my_initializer_list
in the global scope so that theX b{ 1,2,3 };
callsX::X(my_initializer_list<int> lst)
?
So as far as i know, i don't think what you proposed is possible/doable as it requires the {}
to be interpreted differently.
Imagine if you provided more than one my_initializer_list
say like my_initializer_list1
, my_initializer_list2
, my_initializer_list3
and suppose your class has a constructor corrresponding to each of them, then when you use {}
for creating an instance of the class, how can it be decided which of those constructor to select.