2017-10-19 64 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...) {} 
};