Search code examples
c++language-lawyerc++17initializer-listlist-initialization

Deduction guides, initializer_list, and the type deduction process


Consider the following code:

#include <initializer_list>
#include <utility>

template<class T>
struct test
{
    test(const std::pair<T, T> &)
    {}
};

template<class T>
test(std::initializer_list<T>) -> test<T>;

int main()
{
    test t{{1, 2}};
}

I would like to understand why this trick with initializer_list compiles. It looks like at first, {1, 2} is treated as an initializer_list, but then it's re-interpreted as a list-initialization of pair.

What exactly happens here, step-by-step?


Solution

  • It compiles because that's how class template deduction guides work.

    Deduction guides are hypothetical constructors of the type. They don't really exist. Their only purpose is to determine how to deduce class template parameters.

    Once the deduction is made, the actual C++ code takes over with a specific instantion of test. So instead of test t{{1, 2}};, the compiler behaves as if you had said test<int> t{{1, 2}};.

    test<int> has a constructor that takes a pair<int, int>, which can match the values in the braced-init-list, so that's what gets called.

    This kind of thing was done in part to allow aggregates to participate in class template argument deduction. Aggregates don't have user-provided constructors, so if deduction guides were limited to just real constructors, you couldn't have aggregates work.

    So we get to have this class template deduction guide for std::array:

    template <class T, class... U>
    array(T, U...) -> array<T, 1 + sizeof...(U)>;
    

    This allows std::array arr = {2, 4, 6, 7}; to work. It deduces both the template argument and the length from the guide, but since the guide is not a constructor, array gets to remain an aggregate.