initializer_list 和模板类型推导

Posted

技术标签:

【中文标题】initializer_list 和模板类型推导【英文标题】:initializer_list and template type deduction 【发布时间】:2012-09-08 00:43:52 【问题描述】:

考虑函数:

template<typename T>
void printme(T&& t) 
  for (auto i : t)
    std::cout << i;

或任何其他需要一个带有 begin()/end() 参数的函数 - 启用类型。

为什么以下是非法的?

printme('a', 'b', 'c');

当所有这些都合法时:

printme(std::vector<char>('a', 'b', 'c'));
printme(std::string("abc"));
printme(std::array<char, 3> 'a', 'b', 'c');

我们甚至可以这样写:

const auto il = 'a', 'b', 'c';
printme(il);

printme<std::initializer_list<char>>('a', 'b', 'c');

【问题讨论】:

【参考方案1】:

您的第一行 printme('a', 'b', 'c') 是非法的,因为无法推断模板参数 T。如果您明确指定模板参数,它将起作用,例如printme&lt;vector&lt;char&gt;&gt;('a', 'b', 'c')printme&lt;initializer_list&lt;char&gt;&gt;('a', 'b', 'c')

您列出的其他是合法的,因为参数具有明确定义的类型,因此可以很好地推导出模板参数T

您的带有auto 的sn-p 也有效,因为il 被认为是std::initializer_list&lt;char&gt; 类型,因此可以推导出printme() 的模板参数。


这里唯一“有趣”的部分是auto 将选择std::initializer_list&lt;char&gt; 类型,但模板参数不会。这是因为 C++11 标准的 § 14.8.2.5/5 明确指出这是模板参数的非推导上下文:

一个函数形参,其关联实参是一个初始化列表 (8.5.4),但该形参没有 std::initializer_list 或对可能有 cv 限定的 std::initializer_list 类型的引用。 [例子:

template<class T> void g(T);
g(1,2,3); // error: no argument deduced for T

——结束示例]

但是对于 auto,第 7.1.6.4/6 节明确支持 std::initializer_list&lt;&gt;

如果初始化器是 braced-init-list (8.5.4),则为 std::initializer_list&lt;U&gt;

【讨论】:

+1 我学到了一些东西。将std::initialiser_list&lt;&gt; 提升为普通库函数之外的东西。 只是为了完整性。这是解决问题的方法:pastebin.com/huEGwnDt 我们知道为什么会出现这种情况吗?对我来说似乎很奇怪,如果我想允许模板函数(可能是基于范围的算法)采用初始化列表参数,我必须为 std::initializer_list 提供重载。 @JosephMansfield 我还没有找到明确的答案,但我怀疑这与用于统一初始化的大括号有关。调用g(1, 2, 3) 也可以表示g(Foo(1, 2, 3)),其中Foo 是任何类,其构造函数采用三个整数。 @4ZM 如果不带参数调用,调用有歧义。【参考方案2】:

您还可以重载该函数以显式采用 initializer_list 类型的参数。

template<typename T>
void printme(std::initializer_list<T> t) 
  for (auto i : t)
    std::cout << i;

【讨论】:

当然,但这会使其他版本失败,例如printme(std::vector&lt;char&gt;('a', 'b', 'c'));。不幸的是,模板专业化在这里不起作用。 哦,那太好了!谢谢你。我以为我已经尝试过了,但我错了。模板专业化在这里工作得很好。由于该函数可以完全以相同的方式实现,剩下的就是如何弄清楚如何让其中一个调用另一个...... 工作正常。您甚至可以通过为 initializer_list 实现完美转发来改进该解决方案,如下所示:pastebin.com/1ttGniBH?【参考方案3】:

这在 § 14.8.2.5/5 中有明确规定

一个函数参数,其关联参数是 初始化列表但参数没有 std::initializer_list 或对可能具有 cv 资格的引用 std::initializer_list 类型。 [ 例子:

template<class T> void g(T);
g(1,2,3); // error: no argument deduced for T

——结束示例]

要使其工作,您可以显式指定模板参数类型。

printme<std::initializer_list<int>>( 1,2,3,4 );

【讨论】:

以上是关于initializer_list 和模板类型推导的主要内容,如果未能解决你的问题,请参考以下文章

initializer_list

模板别名、变量模板和自动类型推导无法推导模板参数

传递可变数量的实参

参数和返回类型中的模板类型推导

auto类型推导

模板函数类型推导和运算符<<