我正在玩概念(我想给这个问题一个C++ey答案),并遇到了一个让我感到困惑的std::initializer_list行为。 尽管以下代码有效:
#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 auto check(U*u)->decltype(u->begin()!=u->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';
}
输出:
using vector via concept: 422
using initializer_list: 422
initializer_list is iterable: true
但是如果我想添加
// alternative 3
std::cout << "using braced-init-list: " << findmax({9, 255, 86, 4, 89, 6, 1, 422, 5, 29}) << 'n';
程序无法编译。MSVC,gcc和clang给出的原因是"无法推断模板参数"。但是,如果为 std::initializer_list 添加重载,它会:
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;
}
输出:
using braced-init-list: 422
现在,cpp偏好说(在 https://en.cppreference.com/w/cpp/utility/initializer_list)说
std::initializer_list 对象在以下情况下会自动构造...使用大括号初始化列表...作为函数调用参数,相应的赋值运算符/函数接受 std::initializer_list 参数。
initializer_list
与可迭代概念匹配,但是,基于概念的重载接受显式声明的initializer_list变量。 ,这对我来说意味着裸大括号的初始化列表应该生成一个iniitalizer_list并选择基于概念的重载。但事实并非如此。 基于概念的重载接受 std::initializer_list 参数,那么为什么该重载不能从裸大括号的 init-list 中推断并创建一个initializer_list参数呢?
完整的代码在这里,
所以我找到了一个解决方法,强调了这一切是多么奇怪。 如果我们用initializer_list
替换findmax
专业化
template <class C>
inline std::initializer_list<C> const &make_il(std::initializer_list<C> const &i)
{
return i;
}
我们可以在主模板中提示接受
// alternative 3
std::cout << "using make_il: " << findmax(make_il({9, 255, 86, 4, 89, 6, 1, 422, 5, 29})) << 'n';
从大括号的 init-list 创建initializer_list的"魔力"似乎只发生在明确提及initializer_list
时,而不是在查找通用模板参数时发生(即使它受到约束)。 为什么?
表达式{9, 255, 86, 4, 89, 6, 1, 422, 5, 29}
不是隐式的std::initializer_list
。
它的含义和类型旨在从其用法中推断出上下文。
-
如果你把它传递给一个需要
std::initializer_list<int>
的函数,那么就是这样。 -
如果你把它传递给一个需要
std::array<int,10>
的函数,那么就是这样。 -
如果将其传递给需要聚合类型的函数,则就是这样。
但是,您将其作为推导的模板参数传递template <Iterable C> auto findmax(C const &c)
findmax({9, 255, 86, 4, 89, 6, 1, 422, 5, 29})
不会编译,因为函数依赖于调用站点来指定要传递的类型,而调用站点依赖于函数来指定要传递的类型。
std::initializer_list
在这两个位置都没有提及,因此编译器不知道您想要std::initializer_list
。
{..}
没有类型,只能推导出为const T(&)[N]
或(const ref)std::initializer_list<T>
。
findmax
期望的类型不是上述形式,它接受const T&
template <Iterable C> auto findmax(C const &c)
.