在下面的示例中,
#include <iostream>
#include <utility>
//okay:
//
template<typename T, typename F>
decltype(auto) runner(T&& t, F f)
{
return f(std::forward<T>(t));
}
//okay:
//
struct runner_t_f {
template<typename T>
void operator()(T&& t)
{
std::cout<<"template op(): "<<t<<'\n';
}
};
template<typename T>
struct runner_t {
void operator()(T&& t) //error: cannot bind 'T' lvalue to 'T&&'
{
std::cout<<"template functor: "<<t<<'\n';
}
};
int main(void)
{
int j{13};
auto res = runner(j, [](int const& x) -> double { //okay
return static_cast<double>(x*x);
});
std::cout<<res<<'\n';
runner_t_f rtf{};
rtf(j);//okay
runner_t<int> rt{};
rt(j);//not okay...why?
//error: cannot bind ‘int’ lvalue to ‘int&&’
return 0;
}
我尝试使用完美的转发来创建这些runner
蹦床功能。除runner_t
之外,所有编译(和执行)操作均失败,并出现以上代码中注释的错误。为什么?
而且我该如何模拟编译器正在尝试执行的操作,以便我可以理解出了什么问题?预先感谢。
由于引用折叠和模板类型推导规则,转发引用起作用。
在一个简单的函数中:
template <typename T>
void foo(T&& t) {}
foo(42)
时,推导T
为int
,因此T&&
变为int&&
:rvalue-reference-to- int
。int i = 42; foo(i)
时,推导T
为int&
,并且T&&
变为int& &&
。由于您没有对引用的引用,因此引用折叠规则会出现,并为您留下int&
:lvalue-reference-to- int
。这意味着什么:
template<typename T>
struct runner_t {
void operator()(T&& t) //error: cannot bind 'T' lvalue to 'T&&'
{
std::cout<<"template functor: "<<t<<'\n';
}
};
当您指定runner_t<int>
时,T&&
变为int&&
,并且右值引用不能绑定到j
,因为它是左值。
如果您改为将rt
的类型指定为runner_t<int&>
,则t
的类型将为int& &&
,这将折叠为int&
。可以绑定到j
。
要模拟编译器在做什么,您将需要这样的东西:
runner_t<decltype((j))> rt{};
rt(j);
我们需要传递表达式j
的类型(我们使用多余的括号来完成)。这是一个左值,因此您得到T = int&
和T&& = int&
,我们可以将j
传递为。
以前没有用,因为对于T = int
,T&& = int&&
,它只能绑定到右值,因此您必须执行类似rt(int{j})
的操作(顺便说一下,decltype((int{j})) = int
,所以runner_t<decltype((int{j}))>{}(int{j})
仍会工作)