14

在他的“C++ and Beyond 2012:Universal References”演讲中,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 进行通用引用的函数,但是在使用-idiom 时,使用by value 还是 byauto + { expr } => initializer_list甚至都无关紧要。至少对我来说,这种行为完全令人惊讶。哪种行为符合标准?有谁知道这背后的逻辑?initializer_listconst&

4

3 回答 3

9

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

一个人是initializer_list按值还是按const&

foo:对于 any X,两个重载XX&参数对于左值参数是模棱两可的 - 两者都同样可行(对于右值X与 vs相同X&&)。

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

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

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

于 2013-06-26T08:19:12.607 回答
4

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

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

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

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确认 evenf(T&)比左值更专业,f(T&&)并且更适合左值。

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

你的结果的解释是:

foo( {1,2,3} );

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

foo( il );

foo(initializer_list<T>)foo(T&&)比重载决议选择的更专业。

goo( {1,2,3} );

您不能从花括号初始化列表中推断出模板参数,goo(initializer_list<int>)唯一可行的函数也是如此。

goo( il );

il是一个非常量左值,goo(T&&)可以用Tdeduced as调用initializer_list<int>&,所以它的签名是goo(initializer_list<int>&)更好的匹配,goo(initializer_list<int> const&)因为将非常量绑定il到常量引用是比将它绑定到非常量引用更差的转换序列.

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

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

于 2013-06-26T12:36:54.253 回答
0

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

对于goo,这与完美转发是同步的。调用 时,可以在(with ) 和常量引用版本goo(il)之间进行选择。我想用非常量引用调用版本优先于使用 const 引用的更专业的版本。话虽如此,我不确定这是一个明确定义的情况。goo(T&&)T = initializer_list<T>&

编辑:

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

于 2013-06-26T08:27:53.110 回答