【问题标题】:Universal references and std::initializer_list通用引用和 std::initializer_list
【发布时间】:2013-06-26 08:03:51
【问题描述】:

在他的“C++ 和超越 2012:通用引用”演示文稿中,Scott 反复强调这一点,通用引用 处理/绑定到所有内容,因此重载已经采用通用引用参数的函数确实没有意义。 我没有理由怀疑这一点,直到我将它们与std::initializer_list 混合在一起。

这是一个简短的例子:

#include <iostream>
#include <initializer_list>
using namespace std;

template <typename T>
void foo(T&&) { cout << "universal reference" << endl; }

template <typename T>
void foo(initializer_list<T>) { cout << "initializer list" << endl; }

template <typename T>
void goo(T&&) { cout << "universal reference" << endl; }

template <typename T>
void goo(initializer_list<T> const&) { cout << "initializer list" << endl; }

int main(){
    auto il = {4,5,6};
    foo( {1,2,3} );
    foo( il );
    goo( {1,2,3} );
    goo( il );
    return 0;
}

奇怪的是,VC11 Nov 2012 CTP 抱怨模棱两可 (error C2668: 'foo' : ambiguous call to overloaded function)。更令人惊讶的是,gcc-4.7.2、gcc-4.9.0 和 clang-3.4 同意以下输出:

initializer list
initializer list
initializer list
universal reference

因此,显然可以(使用 gcc 和 clang)重载使用 initializer_lists 进行通用引用的函数,但是在使用 auto + { expr } =&gt; initializer_list-idiom 时,无论是通过值还是通过 @ 来获取 initializer_list 都无关紧要987654328@。 至少对我来说,这种行为完全令人惊讶。 哪种行为符合标准?有人知道这背后的逻辑吗?

【问题讨论】:

  • 最后一个是[over.ics.rank]/3 sub-bullet 6的一个例子,说明参数类型较少cv-qualified的重载将被明确使用。
  • 您确定演示文稿中有您声称的一般性声明吗?确实,通用引用会绑定到任何东西,但重载消歧机制(即对于模板)总是将通用引用列为最具体的重载显然不是真的。
  • 略过幻灯片,在第 17 张幻灯片上显示:Overloading + URef almost always an error. Makes no sense: URefs handle everything. [...]
  • @user2523017 好的。用于对模板重载进行排名的规则不容易理解。我可能是错的(尽管 Xeo 的回答似乎同意),但我自然会假设 template &lt;class T&gt; void f(initializer_list&lt;T&gt;)template &lt;class T&gt; void f(T&amp;&amp;) 更专业。使用 GCC 的简单测试似乎也证实了这一点(不一定使用 initializer_list 作为容器)。
  • @jogojapan template &lt;class T&gt; void f(initializer_list&lt;T&gt;) 确实比template &lt;class T&gt; void f(T&amp;&amp;) 更专业,但关键是它不在乎。重载选择首先根据转换顺序进行排名,然后如果还没有最佳重载其他标准包括部分排序(“更专业”)。

标签: c++ c++11


【解决方案1】:

关键在于:从花括号初始化列表 ({expr...}) 推导出类型不适用于模板参数,仅适用于 auto。使用模板参数,你会得到一个推论失败,并且不考虑重载。这导致了第一个和第三个输出。

无论是通过价值还是通过const&amp; 来获取initializer_list 甚至都无关紧要

foo:对于任何X,采用XX&amp; 参数的两个重载对于左值参数来说是模棱两可的 - 两者同样可行X 与 @ 相同987654330@ 用于右值)。

struct X{};
void f(X);
void f(X&);
X x;
f(x); // error: ambiguous overloads

但是,部分排序规则在这里介入(第 14.5.6.2 节),采用泛型 std::initializer_list 的函数比采用任何东西的泛型更专业

goo:对于带有X&amp;X const&amp; 参数和X&amp; 参数的两个重载,第一个更可行,因为第二个重载需要限定转换X&amp;X const&amp;(§13.3.3.1.2/1 表 12 和 §13.3.3.2/3 第三个子项目符号)。

【讨论】:

  • 遗憾的是,如果专业化标准和更可行的标准在使用哪个功能上相互矛盾,该标准似乎没有具体说明会发生什么......
  • @PierreBdR:确实如此,确定最佳可行函数的案例是按照它们出现的顺序来查看的。
【解决方案2】:

如果 Scott 真的说他错了,那是他所教授的误导性“通用参考”心智模型的另一个问题。

所谓的“通用引用”是贪婪的,并且可能会在您不希望或不期望它们匹配时匹配,但这并不意味着它们始终是最佳匹配。

非模板重载可以是完全匹配的,并且会优先于“通用参考”,例如这选择了非模板

bool f(int) { return true; }
template<typename T> void f(T&&) { }
bool b = f(0);

并且模板重载可以比“通用引用”更专业,因此将由重载决议选择。例如

template<typename T> struct A { };
template<typename T> void f(T&&) { }
template<typename T> bool f(A<T>) { return true; }
bool b = f(A<int>());

DR 1164 确认即使f(T&amp;)f(T&amp;&amp;) 更专业,并且对于左值而言将是首选。

在您的两种情况下,initializer_list 重载不仅更加专业化,而且像 {1,2,3} 这样的花括号初始化列表永远无法通过模板参数推导推导出来。

对你的结果的解释是:

foo( {1,2,3} );

您不能从花括号初始化列表中推导出模板参数,因此 foo(T&amp;&amp;) 的推导失败,foo(initializer_list&lt;int&gt;) 是唯一可行的函数。

foo( il );

foo(initializer_list&lt;T&gt;)foo(T&amp;&amp;) 更专业,因此由重载决议选择。

goo( {1,2,3} );

您不能从花括号初始化列表中推断出模板参数,因此goo(initializer_list&lt;int&gt;) 是唯一可行的函数。

goo( il );

il 是一个非 const 左值,goo(T&amp;&amp;) 可以调用 T 推导出为 initializer_list&lt;int&gt;&amp;,所以它的签名是 goo(initializer_list&lt;int&gt;&amp;)goo(initializer_list&lt;int&gt; const&amp;) 更好的匹配,因为绑定非将 const il 绑定到 const 引用是比将其绑定到非 const 引用更糟糕的转换序列。

上面的其中一个 cmets 引用 Scott 的幻灯片中的话说:“毫无意义:URefs 处理一切。”这是真的,这正是你可能想要超载的原因!对于某些类型,您可能需要更具体的函数,而对于其他所有类型,您可能需要通用引用函数。您还可以使用 SFINAE 来约束通用引用函数以停止它处理某些类型,以便其他重载可以处理它们。

对于标准库中的示例,std::async 是一个采用通用引用的重载函数。一个重载处理第一个参数是std::launch 类型的情况,另一个重载处理其他所有内容。 SFINAE 防止“其他所有”重载贪婪地匹配将 std::launch 作为第一个参数传递的调用。

【讨论】:

    【解决方案3】:

    好的,所以首先对foo 的反应是有道理的。 initializer_list&lt;T&gt; 匹配两个调用并且更专业,因此应该这样调用。

    对于goo,这与完美转发同步。在调用goo(il) 时,可以在goo(T&amp;&amp;)(与T = initializer_list&lt;T&gt;&amp;)和常量引用版本之间进行选择。我想用非常量引用调用版本优先于使用 const 引用的更专业的版本。话虽这么说,我不确定这是一个明确定义的情况w.r.t。标准。

    编辑:

    请注意,如果没有模板,这将由标准的第 13.3.3.2 段(排序隐式转换序列)解决。这里的问题是,AFAIK,模板函数的部分排序将决定第二个(更专业的)goo(initializer_list&lt;T&gt; const&amp;) 将被调用,但隐式转换序列的排名将决定goo(T&amp;&amp;) 将被调用。所以我想这是一个模棱两可的案例。

    【讨论】:

    • @DyP:这里没有非模板函数。
    • @Xeo 哦,我太累了......是的
    • @DyP 我知道,但是模板使事情变得复杂。我添加了一个注释来解释这一点。
    • @PierreBdR:隐式转换序列在部分排序之前处理,使第一个重载更可行。
    • @Xeo 我会选择“鉴于这些定义,一个可行的函数 F1 被定义为比另一个可行的函数 F2 更好的函数如果对于所有参数i,ICS*i*(F1) 不是比ICSi(F2)差的转换序列,然后" ; )
    猜你喜欢
    • 1970-01-01
    • 2012-04-09
    • 2021-12-21
    • 1970-01-01
    • 1970-01-01
    • 2020-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多