2012-12-25 61 views
9

我有一個翻譯一個std ::元組爲模板參數包

typedef std::tuple<A, B> TupleType; 

,並想用類 列表中的「模板」。

假設我有:

template<typename... args> 
std::tuple<args...> parse(std::istream &stream) { 
    return std::make_tuple(args(stream)...); 
} 

,我也可以成功地使用它:

auto my_tuple = parse<A, B>(ifs); 

,纔有可能避免指定類列表A,B,如果我已經有一個

typedef std::tuple<A,B> TupleType; 

哪裏列表A,B已經存在?

一個例子:

#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE 
#include <iostream> // std::cerr 
#include <fstream> // std::ifstream 
#include <tuple> // std::tuple 

class A { 
public: 
    A(std::istream &); // May throw FooBaarException 
}; 

class B { 
public: 
    B(std::istream &); // May throw FooBaarException 
}; 

template<typename... args> 
std::tuple<args...> parse(std::istream &stream) { 
    return std::make_tuple(args(stream)...); 
} 

int main() { 
    std::ifstream ifs; 
    ifs.exceptions(ifstream::eofbit | ifstream::failbit | ifstream::badbit); 
    int res = EXIT_FAILURE; 
    try { 
    ifs.open("/some/file/path", std::ios::in | std::ios::binary); 
    auto my_tuple = parse<A, B>(ifs); // my_tuple is of the type std::tuple<A,B> 
    /* Here do something interesting with my_tuple */ 
    res = EXIT_SUCCESS; 
    } catch (ifstream::failure e) { 
    std::cerr << "error: opening or reading file failed\n"; 
    } catch (FooBaarException e) { 
    std::cerr << "error: parsing in a constructor failed\n"; 
    } 
    return res; 
} 
+1

看來,在構造函數中,你想從字符串中讀取。請注意,構造函數被調用的順序未被指定用於執行'parse'。 –

+0

@ JohannesSchaub-litb,有趣的一點。也可以這樣做:http://liveworkspace.org/code/MTk2Nj$0提供的組件類型是不同的(可能但是太長以至於不能顯示爲重複類型的示例)。 – rici

回答

5

轉換算在你的情況根本的問題似乎是,你想專門的函數模板parse的特殊情況下,當模板參數是std::tuple的。不幸的是,這種專業化對功能模板來說是不可能的。

但是,它可能與類模板。

因此,作爲第一步,你可以定義parsestruct的靜態函數,就像這樣:

using std::istream; 
using std::tuple; 
using std::make_tuple; 

struct A { A(const istream &) {} }; 
struct B { B(const istream &) {} }; 

template <typename... Args> 
struct parser 
{ 
    /* Your original function, now inside a struct. 
    I'm using direct tuple construction and an 
    initializer list to circumvent the order-of- 
    construction problem mentioned in the comment 
    to your question. */ 
    static tuple<Args...> parse(const istream &strm) 
    { return tuple<Args...> {Args(strm)...}; } 
}; 

template <typename... Args> 
struct parser<tuple<Args...>> 
{ 
    /* Specialized for tuple. */ 
    static tuple<Args...> parse(const istream &strm) 
    { return parser<Args...>::parse(strm); } 
}; 

然後你可以把它在希望的方式:

int main() 
{ 
    typedef tuple<A,B> tuple_type; 
    auto tup = parser<tuple_type>::parse(std::cin); 
    return 0; 
} 

作爲第二步,您可以定義一個函數模板(再次),將參數傳遞給結構的右側特化:

template <typename... Args> 
auto parse(const istream &strm) -> decltype(parser<Args...>::parse(strm)) 
{ return parser<Args...>::parse(strm); } 

現在你可以在正是你想要的方式使用它:

int main() 
{ 
    typedef tuple<A,B> tuple_type; 
    auto tup = parse<tuple_type>(std::cin); 
    return 0; 
} 

(你仍然可以使用它以舊的方式,太:auto tup = parse<A,B>(std::cin)


備註。正如在parser :: parse()的註釋中提到的,我使用直接元組構造而不是make_tuple來避免元組構造順序的問題。這不是直接關係到你的問題,而是一件好事。見how to avoid undefined execution order for the constructors when using std::make_tuple

+0

「不幸的是,這種專業化對功能模板來說是不可能的。」並不完全正確。嗯,這是事實,但你可以從重載和模板扣除中獲得相同的效果。請看這裏:http://liveworkspace.org/code/MjU4Nj$0 – rici

+0

@rici我不認爲你可以使用重載來解決問題中描述的問題,因爲現有版本的'parse'必須保持完整,是如此籠統,以至於會引起歧義。但是用於結構體/類的機制是部分模板專用化的機制,它可以如上所述消除歧義。 – jogojapan

+0

TBH,我不知道他想做什麼。我多次閱讀這個問題,但我仍然不明白他想呈現的界面。然而,通過添加一個未評估的參數來解析,這當然是可能的,這有時是一種有用的技術。 (例如'auto a = parse(ifs,Into ());',可以在不改變現有接口的情況下添加。)(我同意在這種情況下,結構更好,這就是我自己使用它的原因:)) – rici

1

的基本方法是創建索引的順序0, ..., std::tuple_size<Tuple>::value - 1作爲參數包Indicesparse<typename std::tuple_element<Tuple, Indices>::type...>(stream)調用你的函數。你可能會將邏輯封裝成一個函數parse_tuple<Tuple>(stream)(以及這個代理的一些函數),最終代表parse<...>(stream)

首先,這是一個類模板和一個函數,用於根據std::tuple的大小創建一系列索引。需要該指數從std::tuple獲得類型的列表:

template <int... Indices> struct indices; 
template <> 
struct indices<-1> {    // for an empty std::tuple<> there is no entry 
    typedef indices<> type; 
}; 
template <int... Indices> 
struct indices<0, Indices...> {  // stop the recursion when 0 is reached 
    typedef indices<0, Indices...> type; 
}; 
template <int Index, int... Indices> 
struct indices<Index, Indices...> { // recursively build a sequence of indices 
    typedef typename indices<Index - 1, Index, Indices...>::type type; 
}; 

template <typename T> 
typename indices<std::tuple_size<T>::value - 1>::type const* 
make_indices() { 
    return 0; 
} 

有了這個地方,這是很容易從std::tuple<T...>提取類型的順序:

template<typename T, int... Indices> 
T parse_tuple(std::istream &stream, indices<Indices...> const*) { 
    return parse<typename std::tuple_element<Indices, T>::type...>(stream); 
} 
template <typename T> 
T parse_tuple(std::istream& stream) { 
    return parse_tuple<T>(stream, make_indices<T>()); 
} 
1

有一個標準這種東西的成語。 [1]

// Define the "shape" of the template 
template<typename Tuple> struct TupleMap; 
// Specialize it for std::tuple 
template<typename...T> struct TupleMap<std::tuple<T...>> { 
    using type = std::tuple<T...>; // not necessary but saves typing 
    // ... inside here, you have access to the parameter pac 
} 

下面是使用它,這可能會或可能不適合您的期望(你的例子並不真正表明您預期的使用,因爲它缺乏typedef你在你的問題的承諾)的例子:liveworkspace.org

由於litb提出了這一觀點,所以可以強制元組件按照從左到右的順序構造,從而說明另一個有趣的習慣用法:梳子繼承。見。

(LWS由於可能會再次,誰知道消失了,我會在這裏粘貼以及代碼):

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

// Define the "shape" of the template 
template<typename Tuple> struct TupleMap; 
// Specialize it for std::tuple 
template<typename...T> struct TupleMap<std::tuple<T...>> { 
    using type = std::tuple<T...>; // not necessary but saves typing 

    type value; 

    template<typename Arg> 
    TupleMap(Arg&& arg) 
     : value(T(std::forward<Arg>(arg))...) { 
    } 

    operator type() { return value; } 
}; 

//Try it out: 
using std::get; // Note 2 
using Numbers = std::tuple<char, double, int>; 

// Note 3 
std::ostream& operator<<(std::ostream& out, const Numbers& n) { 
    return out << get<0>(n) << ' ' << get<1>(n) << ' ' << get<2>(n); 
} 

int main() { 
    std::cout << TupleMap<Numbers>(93.14159); 
    return 0; 
} 

[1]至少,我認爲這是一個標準的成語。我使用它很多,並將其視爲「開罐器」模式。

[2]這是需要(或至少,這是我的風格)允許getstd以外定義的元組模板模板一起使用。這樣做可以讓ADL找到get的適當定義,而不必強制我將專門化添加到std::get。通過這種方式,它與beginend的標準ADL成語相似。

[3]你可以搜索一個很酷的黑客來專門爲所有元組設計operator<<。有一種更簡單的方法可以用於特定的元組,但是這對於這個問題來說都是無關緊要的,所以我只是做了一些簡單和無依賴的事情。需要注意的是這個作品,因爲在TupleMap

+0

+1爲使用專業化的結構。 (我發佈自己的答案的原因是,我認爲來自OP代碼的'parse'函數應該轉化爲該結構的靜態函數,而不是調用該結構的構造函數。) – jogojapan

+0

@jogojapan合法的設計決策。儘管如此,我沒有看到它有什麼不同。但味道各不相同。除非你已經看過我在lws上的版本,否則你可能想考慮一下關於建造順序的問題。 – rici