2015-02-11 74 views
4

考虑下面的代码:完美转发和std ::元组

#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 

从它的明显,它不这样做,我想什么输出它要做的,就是我想第三个第四个要经过B.版本的函数。 The std ::转发第四电话是多余的,因为完美的转发不会发生在那里。为了拥有完美的转发我知道:

  • 我必须在一个类型推导上下文
  • 参数的类型的右值引用必须是功能

我明白了一个模板类型这是行不通的。但我不充分掌握:

  • 为什么上下文是通过使用的std ::元组在这样一种方式,它不能根据需要工作改变了吗?为什么模板参数不能用于另一个模板类型的 ?

  • 我该如何(优雅地)修复它?

+0

有更大的问题'B'是你的非const右值引用不能绑定到一个const右值。 – 2015-02-11 09:50:38

+0

我明白了。编译器告诉我同样的事情(修改一些代码)。我只是看不到如何解决它。 – celavek 2015-02-11 09:53:38

+3

没有'g'返回一个const元组? (为什么它会返回一个呢?) – 2015-02-11 09:58:49

回答

8

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

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

要解决这个问题,只需让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 {}; 

然后我们利用这个特点来启用仅元组单参数模板:

// 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 
+0

答案有其优点,因为它让我意识到并阅读更多关于在C++ 11中最好不要以const值返回的事实(我习惯按照Effective C++的建议来做这件事),但是有一个B:不能接受一个“const std :: tuple”,因为它不能从它移动,因为它的签名和转发并不完美,所以涉及到移动语义。所以我的问题仍然没有答案。我可以做你的建议(事实上我尝试过),但这并不能解决我在完美转发环境中的问题。 – celavek 2015-02-12 09:10:24

+0

我明白你的意思了。该函数的实际语义是什么?让一个单独的元组接受元组可能更容易。 – TartanLlama 2015-02-12 12:17:54

+0

实际语义是什么意思?我真正的代码案例就是这样 - 除了函数执行某些操作而不是将某些内容打印到标准输出。你是指具有不同名称的功能?我仍然必须检测到我在某处收到元组的事实。 – celavek 2015-02-12 12:37:47