在Scott Meyers上文提到的文章(link)中,有经验说明:
If a variable or parameter is declared to have type T&& for some deduced type T,that variable or parameter is a universal reference.
实施例1
实际上,使用clang,我们看到以下代码片段将使用-std = c 14成功编译:
#include <utility> template <typename T> decltype(auto) f(T && t) { return std::forward<T>(t); } int x1 = 1; int const x2 = 1; int& x3 = x1; int const& x4 = x2; // all calls to `f` result in a successful // binding of T&& to the required types auto r1 = f (x1); // varIoUs lvalues okay,as expected auto r2 = f (x2); // ... auto r3 = f (x3); auto r4 = f (x4); auto r5 = f (int()); // rvalues okay,as expected
给出任何关于通用引用(转发参考)和类型扣除的描述(参见例如this explanation),这是很清楚的,为什么上述工作.虽然从同样的解释来看,为什么下面的工作也不尽如人意,
(失败)示例2
This question解决了同样的问题.然而,提供的答案不能解释为什么模板类型不被归类为“推导”.
我将要展示的(貌似)满足Meyers上述的要求.但是,以下代码剪切无法编译,产生错误(其中每个调用f):
test.cpp:23:11: error: no matching function for call to ‘f’
auto r1 = f (x1);
test.cpp:5:16: note: candidate function [with T = foo,A = int] not
viable: no known conversion from ‘struct foo< int >’ to ‘foo< int > &&’
for 1st argumentdecltype(auto) f (T< A > && t)
#include <utility> // // It **seems** that the templated type T<A> should // behave the same as an bare type T with respect to // universal references,but this is not the case. // template <template <typename> typename T,typename A> decltype(auto) f (T<A> && t) { return std::forward<T<A>> (t); } template <typename A> struct foo { A bar; }; struct foo<int> x1 { .bar = 1 }; struct foo<int> const x2 { .bar = 1 }; struct foo<int> & x3 = x1; struct foo<int> const& x4 = x2; // all calls to `f` **fail** to compile due // to **unsuccessful** binding of T&& to the required types auto r1 = f (x1); auto r2 = f (x2); auto r3 = f (x3); auto r4 = f (x4); auto r5 = f (foo<int> {1}); // only rvalue works
在上下文中,由于类型T A推论出f的参数,确定参数声明T A&& t将作为通用参考(正向参考).
示例3(为了清楚描述现有问题)
让我强调以下内容:示例2中的代码的失败不是由于struct foo<>是模板类型.失败似乎只是通过将f的参数声明为模板类型.
#include <utility> // // If we re-declare `f` as before,where `T` is no longer a // templated type parameter,our code works once more. // template <typename T> decltype(auto) f (T && t) { return std::forward<T> (t); } // // Notice,`struct foo<>` is **still** a templated type. // template <typename A> struct foo { A bar; }; struct foo<int> x1 { .bar = 1 }; struct foo<int> const x2 { .bar = 1 }; struct foo<int> & x3 = x1; struct foo<int> const& x4 = x2; // all calls to `f` (again) result in // a successful binding of T&& to the required types auto r1 = f (x1); auto r2 = f (x2); auto r3 = f (x3); auto r4 = f (x4);
令我惊讶的是,这个简单的改变完全改变了模板函数f的类型参数的类型扣除的行为.
问题:
为什么第二个例子不按预期工作?有没有技术来克服这个问题与模板类型在c 11/14?有没有众所周知的,现有的代码库(在野外)成功地使用c的向前引用模板类型?
解决方法
int a = 42; f(a);
那么f必须能够接受这样的左值.当f的第一个参数是(lvalue)引用类型时,或者根本不是引用时,情况就是这样:
auto f(int &); auto f(int); // assuming a working copy constructor
当参数是r值引用时,这将不起作用:
auto f(int &&); // error
现在,当您将第一和第三个示例中的转发参考定义为第一个参数时
template<typename T> auto f(T&&); // Showing only declaration
…并且您实际上使用lvalue调用此函数,模板类型扣除将T转换为(lvalue)引用(这可能在我稍后提供的示例代码中看到):
auto f(int & &&); // Think of it like that
当然,上面有太多的参考.所以C有collapsing rules,其实很简单:
> T& &安培;成为T&
> T& &安培;&安培;成为T&
> T&& &安培;成为T&
> T&& &安培;&安培;成为T&&
由于第二条规则,f的第一个参数的“有效”类型是一个左值引用,因此您可以将其值绑定到它.
现在当你定义一个函数g like …
template<template<class> class T,typename A> auto g(T<A>&&);
那么无论什么,模板参数的扣除都必须把T变成一个模板,而不是一个类型.毕竟,你明确指出了当模板参数作为模板< class>类而不是typename.
(这是一个重要的区别,在你的例子中,foo不是一个类型,它是一个模板,你可以看到类型级别的功能,但是回到主题)
现在,T是某种模板.您不能引用模板.
引用(类型)是从(可能不完整的)类型构建的.所以无论什么,T A (它是类型,但不是可以推导出的模板参数)不会变成(左值)引用,这意味着T< A> &安培;&安培;不需要任何崩溃并保留它是什么:一个r值的参考.当然,您不能将左值绑定到rvalue引用.
但是,如果你通过一个价值,那么甚至g将工作.
以上所有可以在以下示例中看到:
template<typename X> struct thing { }; template<typename T> decltype (auto) f(T&& t) { if (std::is_same<typename std::remove_reference<T>::type,T>::value) { cout << "not "; } cout << "a reference" << endl; return std::forward<T>(t); } template< template<class> class T,typename A> decltype (auto) g(T<A>&& t) { return std::forward<T<A>>(t); } int main(int,char**) { thing<int> it {}; f(thing<int> {}); // "not a reference" f(it); // "a reference" // T = thing<int> & // T&& = thing<int>& && = thing<int>& g(thing<int> {}); // works //g(it); // T = thing // A = int // T<A>&& = thing<int>&& return 0; }
关于如何“克服”这个:你不能.至少不是你似乎想要的方式,因为自然解决方案是你提供的第三个例子:既然你不知道传递的类型(这是一个左值引用,一个rvalue引用还是一个引用)?必须保持它像T一样的通用.你可以提供超负荷,但是这样会失败的是完美转发的目的,我猜.
嗯,原来你实际上可以克服这个,使用一些traits类:
template<typename> struct traits {}; template< template<class>class T,typename A> struct traits<T<A>> { using param = A; template<typename X> using templ = T<X>; };
template<typename Y> decltype (auto) g(Y&& t) { // Needs some manual work,but well ... using trait = traits<typename std::remove_reference<Y>::type>; using A = typename trait::param; using T = trait::template templ // using it T<A> copy{t}; A data; return std::forward<Y>(t); }
[…] can you explain why it is not an universal reference? what would the danger or the pitfall of it be,or is it too difficult to implement? I am sincerely interested.
T< a取代;&安培;&安培;不是通用参考,因为T< A>不是模板参数.它是(扣除T和A之后)简单(固定/非通用)类型.
将此作为转发参考的一个严重缺陷将是您不能再表达T&A&&&