Dal*_*ton 11 c++ templates perfect-forwarding c++11 c++14
通用参考(即"前向参考",c++标准名称)和完善的转发c++11,c++14具有许多重要优势; 看到这里,在这里.
在上面引用的Scott Meyers的文章(链接)中,根据经验表明:
如果变量或参数声明为某个推导类型 T的类型为T &&,则该变量或参数是通用引用.
实际上,使用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
Run Code Online (Sandbox Code Playgroud)
给出通用引用(前向引用)和类型推导的任何描述(例如,参见此解释),很清楚为什么上述工作.虽然从同样的解释来看,但为什么以下也不能起作用并不是很清楚.
这个问题解决了同样的问题.但是,提供的答案不能解释为什么模板化类型不被归类为"推断".
我要展示的(貌似)满足迈耶斯上述要求.但是,以下代码剪切无法编译,产生错误(以及每次调用时f):
test.cpp:23:11:错误:调用'f'没有匹配函数
auto r1 = f(x1);
test.cpp:5:16:注意:候选函数[使用T = foo,A = int]不可行:第一个参数没有从'struct foo <int>'转换为'foo <int> &&'的已知转换
decltype(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
Run Code Online (Sandbox Code Playgroud)
在上下文中,由于型T<A>的f的参数被推导,想必参数声明T<A>&& t将表现为一个通用的参考(前向参考).
让我强调以下内容:代码Example 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);
Run Code Online (Sandbox Code Playgroud)
令我惊讶的是,这个简单的更改完全改变了模板函数f的类型参数的类型推导行为.
为什么第二个例子没有按预期工作?是否存在使用模板类型克服此问题的技术c++11/14?有没有众所周知的,现存的代码库(在野外)成功使用c++模板化类型的前向引用?
当你f用一些左值调用某个函数时:
int a = 42;
f(a);
Run Code Online (Sandbox Code Playgroud)
然后f必须能够接受这样的左值.当第一个参数f是(左值)引用类型,或者它根本不是引用时,就是这种情况:
auto f(int &);
auto f(int); // assuming a working copy constructor
Run Code Online (Sandbox Code Playgroud)
当参数是右值引用时,这将不起作用:
auto f(int &&); // error
Run Code Online (Sandbox Code Playgroud)
现在,当您使用转发引用定义一个函数作为第一个参数时,就像您在第一个和第三个示例中所做的那样...
template<typename T>
auto f(T&&); // Showing only declaration
Run Code Online (Sandbox Code Playgroud)
...并且您实际上使用左值调用此函数,模板类型推导变为T(左值)引用(这种情况可以在我稍后提供的示例代码中看到):
auto f(int & &&); // Think of it like that
Run Code Online (Sandbox Code Playgroud)
当然,上面涉及的参考文献太多了.所以C++有折叠规则,实际上非常简单:
T& & 变 T&T& && 变 T&T&& & 变 T&T&& && 变 T&&由于第二个规则,第一个参数的"有效"类型f是左值引用,因此您可以将左值绑定到它.
现在当你定义一个g像...这样的函数时
template<template<class> class T, typename A>
auto g(T<A>&&);
Run Code Online (Sandbox Code Playgroud)
那么不管是什么,模板参数推导必须转T成模板,不是一个类型.毕竟,在将模板参数声明为template<class> class而不是时,您确切地指定了typename.(这是一个重要的区别,foo在你的例子中不是一个类型,它是一个模板......你可以看到它作为类型级别函数,但回到主题)
现在,T是某种模板.您不能引用模板.引用(类型)是从(可能不完整)类型构建的.所以无论如何,T<A>(这是一个类型,但不是可以推导出的模板参数)都不会变成(左值)引用,这意味着T<A> &&不需要任何折叠并保持原样:右值引用.当然,您不能将左值绑定到右值引用.
但如果你传递一个右值,那么甚至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;
}
Run Code Online (Sandbox Code Playgroud)
(住在这里)
关于如何"克服"这个:你不能.至少不是你想要它的方式,因为自然解决方案是你提供的第三个例子:因为你不知道传递的类型(它是左值引用,右值引用还是引用?)你必须保持它的通用性T.你当然可以提供重载,但我想这会以某种方式破坏完美转发的目的.
嗯,结果你实际上可以克服这个,使用一些特征类:
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>;
};
Run Code Online (Sandbox Code Playgroud)
然后,您可以在函数内部提取模板和模板实例化的类型:
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);
}
Run Code Online (Sandbox Code Playgroud)
(住在这里)
[...] 你能解释为什么它不是一个普遍的参考?它的危险或陷阱是什么,或者实施起来太难了?我真的很感兴趣.
T<A>&&不是通用引用,因为T<A>它不是模板参数.这是(均为扣除T和A简单(固定/非通用)型).
使这个转发引用的一个严重缺陷是你不能再表达当前的含义T<A>&&:对T带有参数的模板构建的某种类型的右值引用A.
| 归档时间: |
|
| 查看次数: |
1053 次 |
| 最近记录: |