Search code examples
c++initializer-listautotype-deduction

Using "auto" to deduce the type of a nested initializer list


I'm trying to use auto to automatically deduce the type of a nested std::initializer_list.

auto list = {
    {{ 0, 1}, { 2, 3 }},
    {{ 4, 5}, { 6, 7 }},
};

The actual type here is std::initializer_list<std::initializer_list<std::initializer_list<int>>>, but when I attempt to compile it I get an error stating that auto cannot deduce the type. Is there any way to get auto to recognize such a construct?

I have a program where these initializer lists could be of arbitrary sizes and depths so hardcoding the types is not practical.

Additional Info:

I found this documentation about initializer lists here: https://en.cppreference.com/w/cpp/language/list_initialization

A braced-init-list is not an expression and therefore has no type, e.g. decltype({1,2}) is ill-formed. Having no type implies that template type deduction cannot deduce a type that matches a braced-init-list, so given the declaration template<class T> void f(T); the expression f({1,2,3}) is ill-formed. However, the template parameter can otherwise be deduced, as is the case for std::vector<int> v(std::istream_iterator<int>(std::cin), {}), where the iterator type is deduced by the first argument but also used in the second parameter position. A special exception is made for type deduction using the keyword auto , which deduces any braced-init-list as std::initializer_list in copy-list-initialization.

The documentation seems to suggest that there is a special exception made for type deduction using auto so you would think that this would work... But it seems that when you use a nested list auto cannot deduce the type.


Solution

  • I have a program where these initializer lists could be of arbitrary sizes and depths so hardcoding the types is not practical.

    Then you need to fix that problem.

    You should not think of braced-init-lists as a quick-and-dirty way of making arrays of values without having to think about their types. That's not their purpose. Their purpose is to initialize values. The type std::initializer_list is intended to be an intermediary phase in the process of initializing some type (which is why constructors that take a single initializer_list are given special meaning in list initialization).

    If you want to have arrays of arrays of arrays of various depths and such, then you're going to need to figure out which type that construct needs to be and type it out. auto can only deduce a single level of braced-init-list; you'll need to specify the type(s) explicitly if you need deeper levels.

    there is a special exception made for type deduction using auto

    Yes, there is. But it only applies to deducing a list for auto itself, not for anything that auto's deduction would require.

    In order for auto list = {{1, 2, 3}}; to work, the compiler has to deduce two types: the type to be used for {1, 2, 3} and the type to be used for list. The deduction of the type for list requires deducing the type for the nested braced-init-list. But you can't deduce the type of a braced-init-list. Hence it does not work.

    It should also be noted that, even if it did work, it wouldn't actually work. The reason being that the initializer_list inside of list would refer to a temporary array. A temporary array that would be destroyed at the end of the initializing expression. It's basically the same reason why string_view sv = std::string("foo"); doesn't produce something useful.