【问题标题】:Make a code "forwarding referencable"使代码“转发可参考”
【发布时间】:2016-04-25 14:37:32
【问题描述】:

我打开了this关于转发参考的帖子,这是一个(希望是)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);
}

由于这不是完整的代码,也许我必须根据答案添加额外的代码。

无论如何,正如this 答案中所建议的那样,此版本无法转发引用,因为未推断出Args

所以我的问题是:是否有可能使此代码“转发可引用”?

【问题讨论】:

  • 链接答案不足怎么办?当您明确提供模板参数时,重载是不可行的。

标签: c++ c++11 forwarding-reference


【解决方案1】:

为了完善你的论点,你需要推导出类型。您可以通过分别推导函数的参数和仿函数的参数来做到这一点:

template <typename ReturnType, typename... FunArgs, typename... Args>
ReturnType callFunction(std::function<ReturnType(FunArgs...)> memFunc,
                        Args&&... args) 
{
    //...
}

然后你可以不带模板参数调用callFunction 并推导出所有内容:

mem.callFunction(sortFunc, vec);

【讨论】:

  • 我在the cited answer 上添加了一些来自标准的信息,以解释为什么在function 内有Args... 的情况下模板参数推导失败,也许值得扩展一下关于你的回答;)
  • 我错了,还是这个解决方案类型不安全?我现在无法编辑答案,但你可以想象你会调用 memFunc(args),所以你会在运行时找到这是否合法
  • @Holt 我现在正在打电话,所以请随时编辑此答案或添加您自己的答案:)。
  • @justHelloWorld 为什么要在运行时完成?在编译时检查所有类型。
  • @TartanLlama 你认为在 Holt 考虑之后你的代码仍然可以工作吗?
【解决方案2】:

我将添加一些关于@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&lt;std::string&amp;&gt; 而不是 f&lt;std::string&gt;,但标准告诉你(§14.8.2.1#3 [temp.deduct.调用]):

如果 P 是转发引用并且参数是 lvalue,类型“对A的左值引用”用于代替A进行类型推导。

回到我们最初的 sn-p!

现在,让我们的例子复杂一点:

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&amp;&amp; 的推导类型是 T&amp;,而不是 T,因此 A&lt;T&gt; 的类型推导失败,因为编译器是期待A&lt;std::string&amp;&gt;,而您正在提供A&lt;std::string&gt;

所以现在我们知道我们必须执行以下操作:

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&lt;std::string&gt;

所以问题来了:你要给一个方法一个rvalue,这个方法将把它转发给一个期望lvalue的函数。这没有错,但可能不是你所期望的。

如何处理?

第一种可能是强制第一个参数的类型不管T的推导类型,例如:

template <typename T>
void f (A<typename std::remove_reference<T>::type>, T&&);

但请注意:

  1. 你必须添加更多的东西来处理const
  2. 当第一个参数的类型固定(至少在您的情况下)时,您可能想知道T&amp;&amp; 的用处。

第二种可能(警告:我不知道这是不是标准的!)是将第一个参数移到最后,然后从t推断类型:

template <typename T>
void f (T &&t, A<decltype(std::forward<T>(t))>);

现在,T 的推断类型与 A 的预期类型完全匹配。

不幸的是,我不知道如何使用可变参数模板进行上述操作...

【讨论】:

  • 感谢您的回答。我还有一些疑问:1.f(s); // instanciate f&lt;std::string&amp;&gt;是因为臭名昭著的折叠规则,A&amp; &amp;&amp; becomes A&amp;,对吧? 2.在您的解决方案中,我没有得到A&lt;decltype(std::forward&lt;T&gt;(t))&gt; 3.“警告:我不知道这是否是标准的!”是什么意思那可能是什么问题? 4.您的esample并没有什么不同,因为我们根据@TartanLlama的解决方案使用template&lt;typename T1, typename T2&gt;
  • @justHelloWorld 1. 不是真的,这是因为引用的标准部分:如果您有一个模板函数等待转发引用 T&amp;&amp; 并且您给它一个左值(s 在此case),那么推导的参数模板将是T&amp; 而不是T(即使对于其他参数!)。这是与传递右值不同的行为,在这种情况下,推导的参数对于其他参数仍然是 T
  • @justHelloWorld 2. 您可以使用t 的推导类型,而不是使用T 作为模板参数,可以使用decltypestd::forward 检索它(将是@987654364 @ 如果给定参数是左值,T&amp;&amp; 如果是右值)。 3. 我只是说我不知道​​你是否可以在后面的参数的定义中使用参数名称。 4. 我是在扩展您的代码(为什么它不起作用,以及为什么它的行为可能很奇怪),而不是 Tartan 建议的代码。
  • 根据您对第 4 点的回答。似乎@TartanLlama 代码可能是一个可行的解决方案,还是我错了?
  • @justHelloWorld Tartan 代码基本上与std::remove_reference 的代码工作方式相同,但这不是您想要的,我会在答案的末尾添加一点来解释原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-20
  • 2016-08-20
  • 2018-11-20
  • 2012-07-23
  • 2017-04-10
  • 2020-06-23
相关资源
最近更新 更多