打个码"forwarding referencable"
Make a code "forwarding referencable"
我打开了post转发参考,这是一个(希望)MCVE代码:
#include <functional>
#include <vector>
using namespace std;
struct MultiMemoizator {
template <typename ReturnType, typename... Args>
ReturnType callFunction(std::function<ReturnType(Args...)> memFunc, Args&&... args) {
}
};
typedef vector<double> vecD;
vecD sort_vec (const vecD& vec) {
return vec;
}
int main()
{
vecD vec;
std::function<vecD(const vecD&)> sortFunc(sort_vec);
MultiMemoizator mem;
mem.callFunction<vecD, vecD>(sortFunc, vec);
}
由于这不是全部代码,也许我必须根据答案添加额外的代码。
无论如何,正如 回答中所建议的那样,此版本无法转发引用,因为未推导出 Args
。
所以我的问题是:是否可以编写此代码 "forwarding referencable"?
为了完美转发你的论点,你需要推导类型。您可以通过分别推导函数的参数和函子的参数来做到这一点:
template <typename ReturnType, typename... FunArgs, typename... Args>
ReturnType callFunction(std::function<ReturnType(FunArgs...)> memFunc,
Args&&... args)
{
//...
}
然后你可以在没有模板参数的情况下调用 callFunction
并推导出所有内容:
mem.callFunction(sortFunc, vec);
我将添加一些关于@TartanLlama 回答的详细信息为什么 你的代码无法编译(即使没有明确的模板参数)以及为什么(在我看来)你的代码 危险 .
下面我只用一个简单的类型T
代替你的参数包Args...
因为这样解释起来更简单而且不改变意思
关于转发参考的一点提醒...
首先,让我们举一个比你的例子更简单的例子:
template <typename T>
void f (T&&);
现在,让我们从各种来源实例化 f
,假设有以下变量:
std::string s;
const std::string cs;
...然后:
f(s); // instanciate f<std::string&>
f(cs); // instanciate f<const std::string&>
f(std::string()); // instanciate f<std::string&&>
你应该会疑惑:为什么第一个实例是f<std::string&>
而不是f<std::string>
?,但是标准告诉你(§14.8.2.1# 3 [temp.deduct.call]):
If P is a forwarding reference and the argument is an
lvalue, the type “lvalue reference to A” is used in place of A for type deduction.
回到我们最初的片段!
现在,让我们的例子复杂一点:
template <typename T>
struct A {};
template <typename T>
void f (A<T>, T&&);
还有一个实例化:
std::string s;
A<std::string> as;
f(as, s);
上面相当于你的例子,会编译失败,但是为什么...?好吧,如上所述,当你有一个 lvalue 时,T&&
的推导类型是 T&
,而不是 T
,因此类型推导A<T>
失败,因为编译器期望 A<std::string&>
而你给出 A<std::string>
.
所以现在我们知道我们必须执行以下操作:
A<std::string&> ars;
A<std::string const&> acrs;
f(ars, s); // good
f(acrs, cs); // good
为什么危险?
好的,现在,这应该不错:
A<std::string&&> arrs;
f(arrs, std::string());
但它不是...因为当 T
被推断为右值引用时,T
只是 T
,所以编译器期望 A<std::string>
。
所以问题来了:你要给一个方法一个 rvalue ,这个方法将把它转发给一个需要 lvalue[= 的函数97=]。这没错,但它可能不是您所期望的。
如何处理?
第一种可能性是强制第一个参数的类型,而不考虑 T
的推导类型,例如:
template <typename T>
void f (A<typename std::remove_reference<T>::type>, T&&);
但注意:
- 您将不得不添加更多内容来处理
const
。
- 当第一个参数的类型固定时(至少在您的情况下是这样),人们可能想知道
T&&
的用处。
第二种可能(warning:不知道这个是不是标准!)是把第一个参数移到最后,然后从[=37推导类型=]:
template <typename T>
void f (T &&t, A<decltype(std::forward<T>(t))>);
现在 T
的推导类型与 A
的预期类型完全匹配。
不幸的是,我不知道如何使用可变模板使上述工作...
我打开了
#include <functional>
#include <vector>
using namespace std;
struct MultiMemoizator {
template <typename ReturnType, typename... Args>
ReturnType callFunction(std::function<ReturnType(Args...)> memFunc, Args&&... args) {
}
};
typedef vector<double> vecD;
vecD sort_vec (const vecD& vec) {
return vec;
}
int main()
{
vecD vec;
std::function<vecD(const vecD&)> sortFunc(sort_vec);
MultiMemoizator mem;
mem.callFunction<vecD, vecD>(sortFunc, vec);
}
由于这不是全部代码,也许我必须根据答案添加额外的代码。
无论如何,正如 Args
。
所以我的问题是:是否可以编写此代码 "forwarding referencable"?
为了完美转发你的论点,你需要推导类型。您可以通过分别推导函数的参数和函子的参数来做到这一点:
template <typename ReturnType, typename... FunArgs, typename... Args>
ReturnType callFunction(std::function<ReturnType(FunArgs...)> memFunc,
Args&&... args)
{
//...
}
然后你可以在没有模板参数的情况下调用 callFunction
并推导出所有内容:
mem.callFunction(sortFunc, vec);
我将添加一些关于@TartanLlama 回答的详细信息为什么 你的代码无法编译(即使没有明确的模板参数)以及为什么(在我看来)你的代码 危险 .
下面我只用一个简单的类型T
代替你的参数包Args...
因为这样解释起来更简单而且不改变意思
关于转发参考的一点提醒...
首先,让我们举一个比你的例子更简单的例子:
template <typename T>
void f (T&&);
现在,让我们从各种来源实例化 f
,假设有以下变量:
std::string s;
const std::string cs;
...然后:
f(s); // instanciate f<std::string&>
f(cs); // instanciate f<const std::string&>
f(std::string()); // instanciate f<std::string&&>
你应该会疑惑:为什么第一个实例是f<std::string&>
而不是f<std::string>
?,但是标准告诉你(§14.8.2.1# 3 [temp.deduct.call]):
If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.
回到我们最初的片段!
现在,让我们的例子复杂一点:
template <typename T>
struct A {};
template <typename T>
void f (A<T>, T&&);
还有一个实例化:
std::string s;
A<std::string> as;
f(as, s);
上面相当于你的例子,会编译失败,但是为什么...?好吧,如上所述,当你有一个 lvalue 时,T&&
的推导类型是 T&
,而不是 T
,因此类型推导A<T>
失败,因为编译器期望 A<std::string&>
而你给出 A<std::string>
.
所以现在我们知道我们必须执行以下操作:
A<std::string&> ars;
A<std::string const&> acrs;
f(ars, s); // good
f(acrs, cs); // good
为什么危险?
好的,现在,这应该不错:
A<std::string&&> arrs;
f(arrs, std::string());
但它不是...因为当 T
被推断为右值引用时,T
只是 T
,所以编译器期望 A<std::string>
。
所以问题来了:你要给一个方法一个 rvalue ,这个方法将把它转发给一个需要 lvalue[= 的函数97=]。这没错,但它可能不是您所期望的。
如何处理?
第一种可能性是强制第一个参数的类型,而不考虑 T
的推导类型,例如:
template <typename T>
void f (A<typename std::remove_reference<T>::type>, T&&);
但注意:
- 您将不得不添加更多内容来处理
const
。 - 当第一个参数的类型固定时(至少在您的情况下是这样),人们可能想知道
T&&
的用处。
第二种可能(warning:不知道这个是不是标准!)是把第一个参数移到最后,然后从[=37推导类型=]:
template <typename T>
void f (T &&t, A<decltype(std::forward<T>(t))>);
现在 T
的推导类型与 A
的预期类型完全匹配。
不幸的是,我不知道如何使用可变模板使上述工作...