Home > Enterprise >  How is the implementation of initializer_list changes the way the compiler reads?
How is the implementation of initializer_list changes the way the compiler reads?

Time:05-24

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 the X b{ 1,2,3 }; calls X::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.

  • Related