C++ 通用参考和标准::初始值设定项列表

C++ 通用参考和标准::初始值设定项列表,c++,c++11,C++,C++11,在他的“C++和超越2012:通用引用”演示中,Scott反复强调了一点,即通用引用处理/绑定到所有内容,因此重载已采用通用引用参数的函数是没有意义的。 我没有理由怀疑这一点,直到我把它们与std::initializer\u list混合在一起 下面是一个简短的例子: #include <iostream> #include <initializer_list> using namespace std; template <typename T> void

在他的“C++和超越2012:通用引用”演示中,Scott反复强调了一点,即通用引用处理/绑定到所有内容,因此重载已采用通用引用参数的函数是没有意义的。 我没有理由怀疑这一点,直到我把它们与
std::initializer\u 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;
}
因此,显然(使用gcc和clang)可以通过
initializer\u list
s重载使用通用引用的函数,但是当使用
auto+{expr}=>initializer\u list
-习惯用法时,甚至可以通过值或
const&
来获取
initializer\u list
。 至少对我来说,这种行为是完全令人惊讶的。
哪些行为符合标准?有人知道这背后的逻辑吗?

关键是:从带括号的init列表(
{expr…}
)推断类型不适用于模板参数,只适用于
auto
。使用模板参数,您将获得一个扣除失败,并且重载将从考虑中移除。这将导致第一个和第三个输出

无论是按值还是按常量&

foo
:对于任何
X
,使用
X
X&
参数的两个重载对于左值参数都是不明确的-这两个重载对于左值参数都是同样可行的(对于
X
X&
对于右值相同)

然而,偏序规则在这里起作用(§14.5.6.2),采用通用
std::initializer_list
的函数比采用任何函数的通用函数更专业


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

Ok,因此首先,对
foo
的反应是有意义的
initializer\u list
匹配两个调用,并且更专业化,因此应以这种方式调用

对于
goo
,这与完美转发同步。调用
goo(il)
时,可以选择
goo(T&&)
(使用
T=initializer\u list&
)和常量参考版本。我猜使用非常量引用调用版本比使用常量引用调用更专业的版本具有更高的优先级。尽管如此,我不确定这是一个定义明确的情况,即标准

编辑:


注意,如果没有模板,则可通过本标准第13.3.3.2段(隐式转换顺序排序)解决。这里的问题是,AFAIK,模板函数的偏序将决定调用第二个(更专业的)
goo(初始值设定项\u list const&)
,但隐式转换序列的排序将决定调用
goo(T&&)
。因此,我想这是一个模棱两可的案例。

如果斯科特真的说他错了,这是他所教授的误导性“普遍参考”思维模式的另一个问题

所谓的“通用引用”是贪婪的,当你不想或不希望它们匹配时,它们可能会匹配,但这并不意味着它们总是最好的匹配

非模板重载可以是精确匹配,并且将优先于“通用参考”,例如,这将选择非模板重载

bool f(int) { return true; }
template<typename T> void f(T&&) { }
bool b = f(0);
无法从带括号的init列表推断模板参数,因此
foo(T&&)
的推断失败,
foo(初始值设定项列表)
是唯一可行的函数

foo( il );
goo( il );
foo(初始值设定项列表)
foo(T&&)
更专业化,因此通过重载解析选择

goo( {1,2,3} );
无法从带括号的init列表推断模板参数,因此
goo(initializer\u list)
是唯一可行的函数

foo( il );
goo( il );
il
是一个非常量左值,
goo(T&&)
可以用
T
调用,推断为
initializer\u list&
,因此它的签名是
goo(initializer\u list&)
这比
goo(initializer\u list const&)更匹配
因为将非常量
il
绑定到常量引用比将其绑定到非常量引用更糟糕的转换顺序

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


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

最后一个是[over.ics.rank]/3子项目符号6的示例,声明参数类型不太符合cv条件的重载将被明确使用。您确定演示文稿中有像您所声称的那样通用的语句吗?确实,通用引用会绑定到任何东西,但重载消除歧义机制(即模板)总是将通用引用一个列为最具体的重载,这显然不是真的。在幻灯片17中,略过幻灯片,它说:
重载+URef几乎总是一个错误。毫无意义:UREF可以处理一切。[…]
@user2523017正常。用于对tem进行排序的规则
goo( {1,2,3} );
goo( il );