我在玩概念(我想给这个问题一个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
现在,cppreference说(在列表)
当.大括号内列表是一个函数调用参数,而相应的赋值运算符/函数接受一个std::initializer_list参数时,就会自动构造一个std::initializer_list对象。
initializer_list
与Iterable概念相匹配,但是基于概念的重载确实接受显式声明的initializer_list变量。,这对我来说意味着,裸露的大括号列表应该生成一个iniitalizer_list,并选择基于概念的重载。但事实并非如此,基于概念的重载可以接受std::initializer_list参数,那么为什么重载不能从带大括号的列表中推断和创建一个initializer_list参数呢?
所以我找到了一个解决办法来强调这一切是多么的奇怪。如果我们将findmax
专门化的initializer_list
替换为
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';
从大括号内列表创建initializer_list的“魔力”似乎只发生在明确提到initializer_list
时,而不是在查找泛型模板参数时(即使它受到约束)。为什么会这样呢?
发布于 2022-09-02 20:11:44
表达式{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
。
发布于 2022-09-02 20:47:52
{..}
没有类型,只能推导为const T(&)[N]
或(const of) std::initializer_list<T>
。
期望来自findmax
的类型不是上述形式,它接受const T&
template <Iterable C> auto findmax(C const &c)
。
https://stackoverflow.com/questions/73587304
复制相似问题