完美转发和std :: tuple

cel*_*vek 4 c++ tuples perfect-forwarding

请考虑以下代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

输出将是:

A. First
B. Second
A. Third
A. Fourth
Run Code Online (Sandbox Code Playgroud)

从输出中可以明显看出它没有做我想做的事情,也就是说我希望ThirdFourth能够完成函数的B.版本.该的std ::前通话是多余的完美转发并不能有发生.为了获得完美的转发,我知道:

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

我明白它不起作用.但我没有完全掌握:

  • 为什么通过使用std :: tuple来改变上下文,使得它无法按预期工作?为什么模板参数不能是另一个模板类型的类型?

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

Tar*_*ama 8

你的问题是在第三和第四你传递的const std::tuple地方B.期望一个非const版本.

当编译器试图生成调用代码f,它认为你是一个呼叫const std::tuple等演绎的类型Args...const std::tuple.调用B.无效,因为变量具有与预期不同的const限定.

要解决这个问题,只需g()返回一个非const元组.


编辑:

为了完美转发,您需要一个推断的上下文,正如您在问题中所说的那样.当你std::tuple<Args...>&&在函数参数列表中说,Args...是推导出来的,但std::tuple<Args...>&&不是; 它只能通过右值参考.为了解决这个问题,该参数必须是这样的形式T&&,其中T,推导出.

我们可以使用自定义类型特征来完成此任务:

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

template <typename... Args>
struct is_tuple <std::tuple<Args...>> : std::true_type {};
Run Code Online (Sandbox Code Playgroud)

然后我们使用此特征仅为元组启用单参数模板:

// 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";
}
Run Code Online (Sandbox Code Playgroud)

或者:

//! 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>;
Run Code Online (Sandbox Code Playgroud)

is_specialization_of取自此处由此问题建议.

现在我们有完美的转发!

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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

  • `typename = typename std :: enable_if <is_tuple <T> :: value>`出于多种原因是错误的:1.你不使用嵌套的名称说明符,因此它不会导致替换失败,2. T`可以推导为左值参考,因此您的特性无法测试它.基于函数的decltype转发也没有多大意义 (2认同)