为什么initializer_list不被推导出为它匹配的概念的论据?



我正在玩概念(我想给这个问题一个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).

相关内容

最新更新