2015-02-11 40 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