Please help me to understand why a std::initializer_list
literal can't be deduced as a template parameter? AFAIK, there's no notion of an init-list literal in the language yet, but then why/how does this work?
auto il = { 1, 2, 3, 4, 5 };
Here is my code:
import <iostream>;
import <string>;
import <vector>;
template <typename T>
constexpr auto type_name() {
std::string_view name, prefix, suffix;
#ifdef __clang__
name = __PRETTY_FUNCTION__;
prefix = "auto type_name() [T = ";
suffix = "]";
#elif defined(__GNUC__)
name = __PRETTY_FUNCTION__;
prefix = "constexpr auto type_name() [with T = ";
suffix = "]";
#elif defined(_MSC_VER)
name = __FUNCSIG__;
prefix = "auto __cdecl type_name<";
suffix = ">(void)";
#endif
name.remove_prefix(prefix.size());
name.remove_suffix(suffix.size());
return name;
}
template< typename T >
int SAI_BF_helper(T&&) { return 0; }
int SAI_BF_helper(int i) { return i; }
// function that take any number parameters of any type and then return sum of all ints using binary left folding expressions
template< typename ... Types >
¡int SumAllInts_BinaryLeftFold(Types ... args)
{
return (0 ... SAI_BF_helper(args));
}
template< typename T >
void PrintTypeName(T&& t)
{
std::cout << type_name< decltype( std::forward< T >(t) )> () << std::endl;
}
// if this overload is removed then 'PrintTypeName({1,2,3});' code will not compile
template< typename T >
void PrintTypeName( std::initializer_list< T >&& t)
{
std::cout << type_name< decltype(std::forward< std::initializer_list< T > >(t))>() << std::endl;
}
int main()
{
std::vector< int > numbers{ 1, 2, 3 };
auto il = { 1, 2, 3, 4, 5 };
PrintTypeName(numbers); // output: class std::vector<int,class std::allocator<int> >&
PrintTypeName(il); // output: class std::initializer_list<int>&
PrintTypeName({1,2,3}); // output: class std::initializer_list<int>&&
std::cout << SumAllInts_BinaryLeftFold() << std::endl; // 0
std::cout << SumAllInts_BinaryLeftFold("", 0, 1, 2.2, 'a', "char*", 10, std::string("str"), numbers, il) << std::endl; // 11
//std::cout << SumAllInts_BinaryLeftFold( 1, {1, 2}) << std::endl; // MSVC error message: 'initializer list': is not a valid template argument for 'Types'
}
CodePudding user response:
The short answer: initializer lists (the language mechanic, not the type) are magic. No, really.
The C standard has explicit wording for initializer-list syntax to produce a std::initializer_list
object in certain circumstances such as constructing an auto
-parameter, but an initializer list expression is not itself always a std::initializer_list
; it depends on the circumstances.
This distinction is needed because the same syntax may be used in place of implicit construction for cases like parameters. For example, consider the following code:
auto do_something(Person) -> void;
...
do_something({}); // Calls Person() -- not Person(std::initializer_list<U>)
In this, {}
isn't meant to be a std::initializer_list
object; it's an implicit default-construction of Person
.
Because these cases exist, deducing the exact type of a brace-enclosed initializer list is not so straight-forward. For example:
template <typename T>
auto do_something(T) -> void;
do_something({1}) -> void;
In the above case, what should T
be? Is it do_something<std::initializer_list<int>>
? do_something<int>
with int{1}
? What if this were {1, 2U}
? It can get complicated.
CodePudding user response:
There is no such thing as a "std::initializer_list
literal". {1, 2, 3}
is a braced-init-list; a syntactic construct with no type that can be used to initialize many types. There are a few specific situations (see below) where a std::initializer_list
will be implicitly created from a braced-init-list, but they are not the same thing.
From cppreference :
A
std::initializer_list
object is automatically constructed when:
- a braced-init-list is used to list-initialize an object, where the corresponding constructor accepts an
std::initializer_list
parameter- a braced-init-list is used as the right operand of assignment or as a function call argument, and the corresponding assignment operator/function accepts an
std::initializer_list
parameter- a braced-init-list is bound to
auto
, including in a ranged for loop
auto il = {1, 2, 3, 4, 5};
This is one of the situations when a std::initializer_list
will be implicitly created from a braced-init-list. A braced-init-list is being bound to auto
.
template <typename T>
void PrintTypeName(std::initializer_list<T>&& t)
{
...
}
PrintTypeName({1,2,3});
This is another of the specific situations where a braced-init-list is automatically converted to std::initializer_list
. A braced-init-list is being used as a function call argument and the corresponding function accepts a std::initializer_list
parameter.
template<typename T>
void PrintTypeName(T&& t)
{
...
}
PrintTypeName({1,2,3});
This is not one of the situations where a braced-init-list will automatically be converted to a std::initializer_list
. This PrintTypeName
does not specifically accept a std::initializer_list
.