2017-10-19 51 views
1

我試圖創建一個元組模擬以訪問其元素與相應的標記類型,而不是索引。我想出了一個解決方案(簡化):C++:帶標記類型訪問的元組

template<class T> struct tag { using type = T; }; 
using r = tag<double>; 
using t = tag<double>; 
using c = tag<int>; 
template<class... Ts> class S 
{ 
    std::tuple<typename Ts::type&&...> data; 
public: 
    S(typename Ts::type&&... args) : data(std::forward<typename Ts::type>(args)...) {} 
}; 
int main() 
{ 
    r::type r0 = 0.; 
    const t::type t0 = 1.; 
    auto S0 = S<r, t, c>(r0, t0, 2); // <- error here 
    //auto T0 = std::forward_as_tuple(r0, t0, 2); // <- works! 
} 

然而,它沒有編譯器(gcc 7.2):

error: cannot bind rvalue reference of type ‘tag<double>::type&& {aka double&&}’ to lvalue of type ‘tag<double>::type {aka double}’ 
auto S0 = S<r, t, c>(r0, t0, 2); 
          ^
note: initializing argument 1 of ‘S<Ts>::S(typename Ts::type&& ...) [with Ts = {tag<double>, tag<double>, tag<int>}]’ 
S(typename Ts::type&&... args) : data(std::forward<typename Ts::type>(args)...) {} 
^ 

我發現std::forward_as_tuple功能,能夠正確地推斷參數類型,所以我點是爲我的班級做同樣的事情。任何暗示我做錯了什麼?

UPD:初始描述不完整,抱歉。我的意圖不是存儲副本,而是引用(對於非const參數非const,對於const和rvalue引用const類似於std::forward_as_tuple)。請參閱下面的更新代碼中的註釋:

template<class... Ts> class S 
{ 
    std::tuple<typename Ts::type...> data; 
public: 
    template<class... Args> 
     S(Args&&... args) : data(std::forward<Args>(args)...) {} 
    template<size_t I> auto& get() 
    { 
     return std::get<I>(data); 
    } 
}; 

int main() 
{ 
    r::type r0 = 0.; 
    const t::type t0 = 1.; 
    auto S0 = S<r, t, c>(r0, t0, 2); 
    S0.get<0>() = 111; // <- r0 is not changed! 
    S0.get<1>() = 222; // <- must not be possible! 

    auto T0 = std::forward_as_tuple(r0, t0, 2); 
    std::get<0>(T0) = 333; // <- r0 == 333 
    std::get<1>(T0) = 444; // <- compile error -- can't change const! 
} 
+1

std :: tuple akready很好玩。 –

+2

'typename Ts :: type &&'實際上是右值引用而不是轉發引用。 – Jarod42

+0

@ n.m。不完全 - 我的目標是通過標記類型獲取值,而不是索引。標籤是可區分的,但實際類型不是 –

回答

0

我想我找到了解決方案。這個想法是建議使用make-function來創建一個S對象,如@VTT所示。這個make-function複製類型修飾符(ref,const ref)並將它們添加到S模板參數中。然後,在S內部,使用幾個conditional_t模板將這些類型修飾符複製回元組參數(數據)。 decay_t使用只是爲了獲得純粹的類型本身,而不是對它的引用(否則編譯器,gcc72,說error: ‘tag<double>&’ is not a class, struct, or union type; clang38是一個更好一點:: error: type 'tag<double> &' cannot be used prior to '::' because it has no members

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

using namespace std; 

// additional parameter is needed to distinguish r and t 
template<class T, char c> struct tag { using type = T; }; 
using r = tag<double, 'r'>; 
using t = tag<double, 't'>; 
using c = tag<int, 'c'>; 
//... 

namespace details 
{ 
    template<size_t N, class T, class... Ts> 
    struct index_impl { 
     static constexpr size_t value = N; }; 
    template<size_t I, class T, class Ti, class... Ts> 
    struct index_impl<I, T, Ti, Ts...> { 
     static constexpr size_t value = 
     std::is_same<T, Ti>::value? I : index_impl<I + 1, T, Ts...>::value; }; 

    template<class T, class... Ts> 
    struct index { 
     static constexpr size_t value = index_impl<0, T, Ts...>::value; }; 

    template<class T, class... Ts> 
    static constexpr size_t index_v = index<T, Ts...>::value; 
} // namespace details 

template<class... Ts> class S 
{ 
    std::tuple< 
    std::conditional_t< 
     std::is_reference<Ts>::value, 
     std::conditional_t< 
     std::is_const<std::remove_reference_t<Ts>>::value, 
     const typename std::decay_t<Ts>::type&, 
     typename std::decay_t<Ts>::type& 
     >, 
     typename std::decay_t<Ts>::type 
    >... 
    > data; 
public: 
    template<class... Args> 
    S(Args&&... args) : data(std::forward<Args>(args)...) {} 

    template<class T> auto& get() 
    { 
    return std::get<details::index_v<T, std::decay_t<Ts>...>>(data); 
    } 
}; 

template<class... Ts, class... Args> auto make_S(Args&&... args) 
{ 
    return S< 
    std::conditional_t< 
     std::is_reference<Args>::value, 
     std::conditional_t< 
     std::is_const<std::remove_reference_t<Args>>::value, 
     const Ts&, Ts& 
     >, 
     Ts 
    >... 
    >(std::forward<Args>(args)...); 
} 

int main() 
{ 
    r::type r0 = 0; 
    const t::type t0 = 1; 
    auto S0 = make_S<r, t, c>(r0, t0, 0); 
    S0.get<r>() = 111; 
    //S0.get<t>() = 222; // <- must not be possible! 
    S0.get<c>() = 333; 

    auto T0 = std::forward_as_tuple(r0, t0, 2); 
    std::get<0>(T0) = 444; // <- r0 == 333 
    //std::get<1>(T0) = 555; // <- compile error -- can't change const! 
    std::get<2>(T0) = 666; 
} 

我敢肯定,這個想法可以實現更好,然而這個解決方案工作!例如,我不太明白爲什麼is_const不能與引用一起工作,因此我必須從使用remove_reference_t的類型中刪除它們。因此最終的解決方案是IMO過於複雜。

1

您需要聲明構造函數模板:

#include <utility> 
#include <tuple> 

template<class T> struct tag { using type = T; }; 
using r = tag<double>; 
using t = tag<double>; 
using c = tag<int>; 
template<class... Ts> class S 
{ 
    std::tuple<typename Ts::type...> data; // there is no need to use && here as we want tuple to contain items "as is", not references 
public: 
    template<typename... TArgs> 
    S(TArgs && ... args) : data(std::forward<TArgs>(args)...) {} 
}; 
int main() 
{ 
    r::type r0 = 0.; 
    const t::type t0 = 1.; 
    auto S0 = S<r, t, c>(r0, t0, 2); // <- error here 
    static_cast<void>(S0); // not used 
    //auto T0 = std::forward_as_tuple(r0, t0, 2); // <- works! 
    return(0); 
} 

Run this code online

我想提一個問題:這種元組實際上不會讓你通過標籤類型訪問元素,因爲它允許標籤重複。如果您需要通過標記類型訪問,則需要更徹底地檢查標記< - >值類型關聯。您可能還需要檢查existing tagged-tuple implementation

+0

簡單和工作,謝謝@VTT!但是,在這種情況下數據存儲副本,而不是引用。所以它不是我所期望的'forward_as_tuple'的完整模擬。如果不重新創建整個元組類,甚至有可能嗎? –

+0

@GenaBug這段代碼根本不是'forward_as_tuple'的類似物。首先,'forward_as_tuple'是一個函數模板,而不是一個類模板。如果你想存儲引用,那麼你可以重新定義用來填充元組的類型'使用r = tag ;'如果你想自動將原始類型轉換爲兼容的引用類型,那麼你需要編寫一個專用的模板函數。 – VTT

0

Ts::type&&不是轉發參考,因爲Ts::type不是模板參數,因此,&&引用右值引用。你不能將一個左值綁定到右值引用,因此是錯誤。

std::forward_as_tuple工作原理是因爲您只將參數存儲在「轉發元組」中,而不是將它們存儲在S中。

只要改變他們得到const&參考,因爲它也可以綁定到右值以及爲左值,如果你想存儲引用(我會認爲你這樣做):

template<class... Ts> class S 
{ 
    std::tuple<const typename Ts::type&...> data; 
public: 
    S(const typename Ts::type&... args) : data(args...) {} 
};