【问题标题】:How to perform tuple arithmetic in C++ (c++11/c++17)?如何在 C++ (c++11/c++17) 中执行元组算术?
【发布时间】:2017-11-09 19:04:48
【问题描述】:

我正在尝试编写模板函数/运算符,例如+,用于在两个相同类型的元组之间进行算术运算。例如,对于

std::tuple<int,double> t = std::make_tuple(1,2);

我想做

auto t1 = t + t;  

逻辑很简单:按元素进行算术运算。但我不知道如何在 c++ 模板编程(c++11/17)中进行这项工作。我下面的代码不能用g++ -std=c++11 tuple_arith.cpp 编译。特别是,我想不出让通用 add 函数 (template&lt;typename T&gt; T add(T x, T y) { return x + y; }) 与元组操作代码一起工作的正确方法。

谁能帮忙解释一下如何解决这个问题?

#include <tuple>

namespace std {
  template<typename _Tp, size_t __i, size_t __size, typename _opT >
     struct __tuple_arith {
       static constexpr _Tp  __op(const _Tp& __t, const _Tp& __u, const _opT& op)  {
         return std::tuple_cat(std::make_tuple(op(std::get<__i>(__t), std::get<__i>(__u))
                               , __tuple_arith<_Tp, __i + 1, __size, _opT>::__op(__t, __u)));
       }
     };

  template<typename _Tp, size_t __size, typename _opT>
  struct __tuple_arith<_Tp, __size, __size - 1, _opT> {
       static constexpr _Tp __op(const _Tp& __t, const _Tp& __u, const _opT& op) {
         return std::make_tuple(op(std::get<__size-1>(__t), std::get<__size -1>(__u)));
       }
  };

  template<typename T> T add(T x, T y) { return x + y; }

  template<typename... _TElements> constexpr tuple<_TElements...>
  operator+(const tuple<_TElements...>& __t, const tuple<_TElements...>& __u) {
    using op = __tuple_arith<tuple<_TElements...>, 0, sizeof...(_TElements), decltype(add)>;
    return op::__op(__t, __u, add);
  }
}; //namespace std

#include <iostream>
using namespace std;

int main() {
  std::tuple<int,double> t = std::make_tuple(1,2);
  auto t1 = t + t;
  cout << std::get<0>(t1) << std::endl;
  return 0;
}

具体错误有:

tuple_arith.cpp:14:10: error: template argument ‘(__size - 1)’ involves template parameter(s)
   struct __tuple_arith<_Tp, __size, __size - 1, _opT> {
          ^
tuple_arith.cpp: In function ‘constexpr std::tuple<_Elements ...> std::operator+(const std::tuple<_Elements ...>&, const std::tuple<_Elements ...>&)’:
tuple_arith.cpp:24:90: error: decltype cannot resolve address of overloaded function
  __tuple_arith<tuple<_TElements...>, 0, sizeof...(_TElements), decltype(add)>;
                                                                            ^
tuple_arith.cpp:24:91: error: template argument 4 is invalid
  __tuple_arith<tuple<_TElements...>, 0, sizeof...(_TElements), decltype(add)>;
                                                                             ^
tuple_arith.cpp:25:12: error: ‘op’ has not been declared
     return op::__op(__t, __u, add);
            ^
tuple_arith.cpp: In instantiation of ‘constexpr std::tuple<_Elements ...> std::operator+(const std::tuple<_Elements ...>&, const std::tuple<_Elements ...>&) [with _TElements = {int, double}]’:
tuple_arith.cpp:34:17:   required from here
tuple_arith.cpp:26:3: error: body of constexpr function ‘constexpr std::tuple<_Elements ...> std::operator+(const std::tuple<_Elements ...>&, const std::tuple<_Elements ...>&) [with _TElements = {int, double}]’ not a return-statement
   }
   ^

-- 更新--

感谢您迄今为止提供的有用答案。是否可以使其适用于任何 Operator Wrappers,例如std::{plus,minus,multiplies,divides}?这就是我试图通过模板参数typename _opT 实现的目标。最后,我正在寻找一个可以将兼容的运算符作为参数的函数/对象。

【问题讨论】:

  • 将自己的函数注入namespace std是违法的。标识符以___ 开头后跟大写字母是非法的。这些不是您的代码无法工作的原因,大多数编译器不会要求您这样做,但您也不应该这样做。
  • 只是:ideone.com/giSbxM 有什么问题?
  • @gurka Ostensibly OP 正在寻找一种解决方案,可以优雅地扩展到具有多于或少于两个元素的 tuples。
  • 除非可能没有放在命名空间std中。但是,在某些情况下是允许的:en.cppreference.com/w/cpp/language/extending_std
  • @Yakk -- 它的名称包含两个连续的下划线,而不仅仅是以两个连续的下划线开头。 (另外,正如您所说,以下划线后跟大写字母的名称)

标签: c++ c++11 variadic-templates c++17 stdtuple


【解决方案1】:

您的代码中的问题是您不能部分特化基于另一个模板值的模板值;你可以解决这个问题,但是......为什么?

std::index_sequence得到你想要的不是那么简单

#include <tuple>
#include <iostream>

template <typename ... Ts, std::size_t ... Is>
std::tuple<Ts...> sumT (std::tuple<Ts...> const & t1,
                        std::tuple<Ts...> const & t2,
                        std::index_sequence<Is...> const &)
 { return { (std::get<Is>(t1) + std::get<Is>(t2))... }; }

template <typename ... Ts>
std::tuple<Ts...> operator+ (std::tuple<Ts...> const & t1,
                             std::tuple<Ts...> const & t2)
 { return sumT(t1, t2, std::make_index_sequence<sizeof...(Ts)>{}); }

int main ()
 {
   std::tuple<int,double> t = std::make_tuple(1,2);
   auto t1 = t + t;
   std::cout << std::get<0>(t1) << std::endl;
   std::cout << std::get<1>(t1) << std::endl;
 }

无论如何...我认为将运算符添加到标准类型不是一个好主意。也许你只能定义一个sumT() 函数。

P.s.:std::index_sequencestd::make_index_sequence 是 c++14/17 的特性;但在 c++11 中模拟它们并不太复杂。

-- 编辑--

OP 询问

非常感谢,是否有可能使任何操作符包装器都可以使用它?请看更新

我想你的意思如下

#include <tuple>
#include <iostream>
#include <functional>

template <typename Op, typename Tp, std::size_t ... Is>
auto opH2 (Op const & op, Tp const & t1, Tp const & t2,
           std::index_sequence<Is...> const &)
 { return std::make_tuple( op(std::get<Is>(t1), std::get<Is>(t2))... ); }

template <typename Op, typename Tp>
auto opH1 (Op const & op, Tp const & t1, Tp const & t2)
 { return opH2(op, t1, t2,
               std::make_index_sequence<std::tuple_size<Tp>{}>{}); }

template <typename ... Ts>
auto operator+ (std::tuple<Ts...> const & t1, std::tuple<Ts...> const & t2)
 { return opH1(std::plus<>{}, t1, t2); }

template <typename ... Ts>
auto operator- (std::tuple<Ts...> const & t1, std::tuple<Ts...> const & t2)
 { return opH1(std::minus<>{}, t1, t2); }

template <typename ... Ts>
auto operator* (std::tuple<Ts...> const & t1, std::tuple<Ts...> const & t2)
 { return opH1(std::multiplies<>{}, t1, t2); }

template <typename ... Ts>
auto operator/ (std::tuple<Ts...> const & t1, std::tuple<Ts...> const & t2)
 { return opH1(std::divides<>{}, t1, t2); }

int main ()
 {
   std::tuple<int,double> t = std::make_tuple(1,2);

   auto t1 = t + t;
   auto t2 = t - t;
   auto t3 = t * t;
   auto t4 = t / t;

   std::cout << std::get<0>(t1) << ", " << std::get<1>(t1) << std::endl;
   std::cout << std::get<0>(t2) << ", " << std::get<1>(t2) << std::endl;
   std::cout << std::get<0>(t3) << ", " << std::get<1>(t3) << std::endl;
   std::cout << std::get<0>(t4) << ", " << std::get<1>(t4) << std::endl;
 }

【讨论】:

  • 元组的 std::plus 特化怎么样?可以吗?那么操作符是一件小事
  • @SeverinPappadeux :不合法;特化必须针对 UDT,而不是标准库类型。
  • @max66,非常感谢,是否有可能使任何操作符包装器都可以使用它?请查看更新。
  • @tinlyx - 答案改进。
  • @tinlyx - 代码中的另一个小改进(恕我直言)。
【解决方案2】:

根据标准,您不能在namespace std 中执行此操作。将用户编写的代码注入std 仅在非常有限的情况下是合法的,而这不是其中之一。

您可以将它放在全局命名空间中,但是当在全局命名空间之外时,如果没有 using ::operator+; 或类似名称,您将无法找到它。即,一个糟糕的计划。

这给您留下了一些选择。您可以实现命名运算符。您可以创建一个标签类型,并声明包含所述标签类型的元组参与您的重载决议。您可以创建一个从 std::tuple 派生的修改后的元组类型,它具有这些运算符。

我会在这里做第二个。这是因为运算符的查找遵循类型所在的命名空间,以及您正在处理的模板类型实例的所有模板参数的命名空间。

其他两个选项(派生类型和命名运算符)可以通过类似的实现来完成。这是,因为它使代码更短。

namespace tuple_operators {
  struct enable{};

namespace tuple_operators {
  struct enable{};

  template<class...Lhs, class...Rhs>
  auto operator+( std::tuple<enable, Lhs...> const& lhs, std::tuple<enable, Rhs...> const& rhs ) {
    return utility::index_upto<sizeof...(Lhs)>()([&](auto...Is){
      using std::get;
      return std::make_tuple<
        enable,
        std::decay_t<
          decltype(get<Is+1>(lhs)+get<Is+1>(rhs))
        >...
      >(
        enable{}, (get<Is+1>(lhs)+get<Is+1>(rhs))...
      );
    });
  }
}

index_upto 在哪里:

namespace utility {
  template<std::size_t...Is>
  auto index_over( std::index_sequence<Is...> ) {
    return [](auto&& f)->decltype(auto) {
      return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
    };
  }
  template<std::size_t N>
  auto index_upto( std::integral_constant<std::size_t, N> ={} ) {
    return index_over( std::make_index_sequence<N>{} );
  }
}

这只是一个包扩展的助手,而无需在包扩展时编写另一个函数。

Live example

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-12
    • 2015-09-17
    • 1970-01-01
    • 1970-01-01
    • 2013-02-19
    • 1970-01-01
    • 2017-10-27
    相关资源
    最近更新 更多