【问题标题】:Perfect forwarding and std::tuple完美转发和 std::tuple
【发布时间】:2015-04-11 14:52:37
【问题描述】:

考虑以下代码:

#include <iostream>
#include <tuple>
#include <utility>

// A.
template <typename... Args>
void f (const char* msg, Args&&... args)
{
    std::cout << "A. " << msg << "\n";
}

// B.
template <typename... Args>
void f (const char* msg, std::tuple<Args...>&& t)
{
    std::cout << "B. " << msg << "\n";
}

struct boo
{
    const std::tuple<int, int, long> g () const
    {
        return std::make_tuple(2, 4, 12345);
    }
};

int main ()
{
    f("First", 2, 5, 12345);
    f("Second", std::make_tuple(2, 5, 12345));

    boo the_boo;
    f("Third", the_boo.g());
    f("Fourth", std::forward<decltype(std::declval<boo>().g())>(the_boo.g()));

    return 0;
}

输出将是:

A. First
B. Second
A. Third
A. Fourth

从输出很明显,它没有做我想做的事,也就是说我希望 ThirdFourth 通过 B. 版本的函数。 Fourth 调用中的 std::forward 是多余的,因为那里不会发生完美的转发。为了有完美的转发,我知道:

  • 我必须在类型推导上下文中有一个右值引用
  • 参数的类型必须是函数的模板类型

我知道它不起作用。但我没有完全掌握:

  • 为什么使用 std::tuple 改变了上下文,导致它无法按预期工作?为什么模板参数不能是类型 对于另一个模板类型?

  • 我怎样才能(优雅地)修复它?

【问题讨论】:

  • B 的更大问题是您的非 const 右值引用无法绑定到 const 右值。
  • 我明白了。编译器告诉我同样的事情(有一点代码修改)。我只是不知道如何解决它。
  • 没有g 返回一个常量元组? (为什么它会返回一个呢?)
  • @T.C.阅读 C++11“Effective C++”系列之前的内容。
  • @celavek,不要返回 const 值,这是一个可以追溯到另一​​个十年并阻止移动语义的坏习惯。我不确定 Meyers 但 Sutter 不再建议返回 const 值。无论如何,它总是有可疑的好处。

标签: c++ tuples perfect-forwarding


【解决方案1】:

您的问题是,在第三和第四中,您传递了一个 const std::tuple,其中 B. 需要一个非常量版本。

当编译器尝试为调用f 生成代码时,它会看到您正在使用const std::tuple 进行调用,因此推断Args... 的类型为const std::tuple。调用 B. 无效,因为变量具有与预期不同的 const 限定。

要解决这个问题,只需让g() 返回一个非常量元组。


编辑:

如您在问题中所说,为了实现完美转发,您需要一个推断的上下文。当你在函数参数列表中说std::tuple&lt;Args...&gt;&amp;&amp;时,Args...被推导出来,但std::tuple&lt;Args...&gt;&amp;&amp;不是;它只能通过右值引用。为了解决这个问题,该参数需要采用T&amp;&amp; 的形式,其中推导出T

我们可以使用自定义类型特征来实现这一点:

template <typename T>
struct is_tuple : std::false_type {};

template <typename... Args>
struct is_tuple <std::tuple<Args...>> : std::true_type {};

然后我们使用这个 trait 为元组启用单参数模板:

// B.
template <typename T, typename = typename std::enable_if<
                          is_tuple<typename std::decay<T>::type>::value
                          >::type>
void f (const char* msg, T&& t)
{
    std::cout << "B. " << msg << "\n";
    std::cout << "B. is lval == " << std::is_lvalue_reference<T>() << "\n";
}

或者:

//! Tests if T is a specialization of Template
template <typename T, template <typename...> class Template>
struct is_specialization_of : std::false_type {};

template <template <typename...> class Template, typename... Args>
struct is_specialization_of<Template<Args...>, Template> : std::true_type {};

template <typename T>
using is_tuple = is_specialization_of<T, std::tuple>;

is_specialization_of 取自 here 并由 this question 建议。

现在我们有了完美的转发!

int main ()
{
    f("First", 2, 5, 12345);
    f("Second", std::make_tuple(2, 5, 12345));

    boo the_boo;
    f("Third", the_boo.g());
    f("Fourth", std::forward<decltype(std::declval<boo>().g())>(the_boo.g()));

    auto the_g = the_boo.g();
    f("Fifth", the_g);

    return 0;
}

输出:

A. First
B. Second
B. is lval == 0
B. Third
B. is lval == 0
B. Fourth
B. is lval == 0
B. Fifth
B. is lval == 1

【讨论】:

  • 答案有其优点,因为它让我意识到并阅读了更多关于在 C++11 中最好不要按 const 值返回的事实(我习惯按照 Effective C++ 建议)但是那里有一个细微的差别:B. 不能接受“const std::tuple”,因为它不能从它移动,因为它涉及移动语义,因为它的签名而不是完美的转发。所以我的问题仍然没有答案。我可以按照您的建议(实际上我尝试过),但这并不能解决我在完美转发方面的问题。
  • 我明白你的意思。该函数的实际语义是什么?有一个单独的用于接受元组可能会更容易。
  • 实际语义是什么意思?我所拥有的真实代码案例正是如此——除了函数做某事而不是将某些内容打印到标准输出。你的意思是一个不同名字的函数?我仍然需要检测到我在某处收到元组的事实。
  • 我怎么没想到呢? :) 。我冒昧地用另一种解决方案来增加答案。谢谢
  • typename = typename std::enable_if&lt;is_tuple&lt;T&gt;::value&gt; 错误的原因有很多:1. 您没有使用嵌套名称说明符,因此不会导致替换失败,2. T 可以推断为左值引用,因此您的特征无法对其进行测试。基于函数的 decltype 的转发也没什么意义
猜你喜欢
  • 1970-01-01
  • 2012-01-06
  • 2015-09-07
  • 2012-06-23
  • 2021-12-28
  • 2014-01-04
  • 1970-01-01
  • 2021-12-17
  • 1970-01-01
相关资源
最近更新 更多