I was playing with concepts (I wanted to give this question a C ey answer) and came across a behavior with std::initializer_list that puzzles me. Although the following code works:
#include<utility>
#include <limits>
#include<iostream>
#include <type_traits>
#include <vector>
#include <initializer_list>
#include <string_view>
template <class C>
struct iterable_sfinae
{
template <class U>
static bool check(U*u)
{
auto i = u->begin();
i;
return (std::declval<C>().end());
}
template <class...>
static void check(...);
static constexpr bool value = !std::is_void_v<decltype(check((C*)0))>;
};
template <class C>
static constexpr bool is_iterable_v = iterable_sfinae<C>::value;
template <class C>
concept Iterable = iterable_sfinae<C>::value;
template <class N>
constexpr bool is_numeric_v = std::numeric_limits<std::decay_t<N>>::is_specialized;
template <class N>
concept Number = std::numeric_limits<std::decay_t<N>>::is_specialized;
// alternative 2
template <Iterable C>
auto findmax(C const &c)
requires is_numeric_v<decltype(*(c.begin()))>
{
auto rv = std::numeric_limits<std::decay_t<decltype(*(c.begin()))>>::lowest();
for (auto e: c)
if (e > rv) rv = e;
return rv;
}
int main() {
std::vector<int> v = {9, 255, 86, 4, 89, 6, 1, 422, 5, 29};
std::cout << "using vector via concept: " << findmax(v) << '\n';
std::initializer_list<int> il = {9, 255, 86, 4, 89, 6, 1, 422, 5, 29};
std::cout << "using initializer_list: " << findmax(il) << '\n';
std::boolalpha(std::cout);
std::cout << "initializer_list is iterable: " << is_iterable_v<std::initializer_list<int>> << '\n';
}
output:
using vector via concept: 422
using initializer_list: 422
initializer_list is iterable: true
But if I want to add
// alternative 3
std::cout << "using braced-init-list: " << findmax({9, 255, 86, 4, 89, 6, 1, 422, 5, 29}) << '\n';
the program won't compile. The reason given by MSVC, gcc, and clang is "Couldn't deduce template argument". But if you add an overload for std::initializer_list, it does:
template <Number N>
auto findmax(std::initializer_list<N> const &c)
{
auto rv = std::numeric_limits<std::decay_t<decltype(*(c.begin()))>>::min();
for (auto e: c)
if (e > rv) rv = e;
return rv;
}
Output:
using braced-init-list: 422
Now, cppreference says (at https://en.cppreference.com/w/cpp/utility/initializer_list) that
A std::initializer_list object is automatically constructed when...a braced-init-list is used...as a function call argument, and the corresponding assignment operator/function accepts an std::initializer_list parameter.
initializer_list
matches the Iterable concept, and the concept-based overload does, however, accept the explicitly-declared initializer_list variable.
, and this implies to me that the bare braced-init-list should generate an iniitalizer_list and select the concept-based overload. But it doesn't.
The concept-based overload accepts a std::initializer_list parameter, so why can't that overload deduce and create an initializer_list argument from the bare braced-init-list?
CodePudding user response:
{..}
has no type and can only be deduced as const T(&)[N]
or (const ref of) std::initializer_list<T>
.
Type expected from findmax
is not of the above form, it accepts const T&
template <Iterable C> auto findmax(C const &c)
.
CodePudding user response:
The expression {9, 255, 86, 4, 89, 6, 1, 422, 5, 29}
is not implicitly a std::initializer_list
.
Its meaning and its type is intended to be contextually inferred from its usage.
If you pass it to a function that expects a
std::initializer_list<int>
, then that's what it is.If you pass it to a function that expects a
std::array<int,10>
, then that's what it is.If you pass it to a function that expects a
std::vector<int>
, then that's what it is.
You, however, are passing it as a deduced template argument template <Iterable C> auto findmax(C const &c)
findmax({9, 255, 86, 4, 89, 6, 1, 422, 5, 29})
won't compile because the function relies on the call site to specify what type is being passed, and your call site is depending on the function to specify what type is being passed.
std::initializer_list
isn't mentioned at either location, so the compiler does not know that you want std::initializer_list
.