c ++:带标记类型访问的元组

时间:2017-10-19 15:57:27

标签: 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用于const和rvalue引用,类似于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!
}

3 个答案:

答案 0 :(得分: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

我想提一下另一个问题:这种元组实际上不会让你按标签类型访问元素,因为它允许标签重复。如果您需要按标记类型进行访问,则需要检查标记&lt; - &gt;值类型关联更彻底。您可能还想查看一些existing tagged-tuple implementation

答案 1 :(得分:0)

Ts::type&&不是转发引用,因为Ts::type不是模板参数,因此&&引用右值引用。您不能将左值绑定到右值引用,因此错误。

std::forward_as_tuple有效,因为您只将参数存储在“转发元组”中,而不是将它们存储在S中。

只需将它们更改为const&引用,因为它也可以绑定到rvalues和lvalues,如果你想存储引用(我假设你这样做):

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

答案 2 :(得分: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。