Search code examples
c++c++11variadic-templatesc++17stdtuple

How to perform tuple arithmetic in C++ (c++11/c++17)?


I'm trying to write template functions/operators such as + for doing arithmetic operations between two tuples of the same type. For example, for

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

I'd like to do

auto t1 = t + t;  

The logic is simple: to do the arithmetic element-wise. But I can't figure out how to make this work in c++ template programming (c++11/17). My code below doesn't compile with g++ -std=c++11 tuple_arith.cpp. In particular, I can't figure out the right way of getting the generic add function (template<typename T> T add(T x, T y) { return x + y; }) to work with the tuple manipulating code.

Can someone help explain how to fix the issue?

#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;
}

The specific errors are:

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
   }
   ^

-- Update --

Thanks for the helpful answers so far. Is it possible to make it work for any Operator Wrappers, e.g. std::{plus,minus,multiplies,divides}? That's what I was trying to achieve with the template parameter typename _opT. In the end, I'm looking for a function/object that can take a compatible Operator as a parameter.


Solution

  • The problem in your code is that you cannot partial specialize a template value based over another template value; you can go round this problem but... why?

    Is't so simple obtain what you want with 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;
     }
    

    Anyway... I don't think it's a good idea add operators to standard types; maybe you can only define a sumT() function.

    P.s.: std::index_sequence and std::make_index_sequence are c++14/17 features; but isn't too complex simulate they in c++11.

    -- EDIT --

    The OP ask

    Thanks a lot, is it possible to make this work for any operators wrappers? Please see the update

    I suppose you mean as follows

    #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;
     }