Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 775
thor Avatar asked Nov 09 '17 19:11

thor


People also ask

Can we perform arithmetic operations on tuple?

Arithmetic operationsIf addition and multiplication can be performed on the entries in a tuple, then addition and scalar multiplication on tuples can be defined.

What is a tuple in C?

Tuples in C++ A tuple is an object that can hold a number of elements. The elements can be of different data types.

Can you iterate through a tuple C++?

A C++ tuple is a container that can store multiple values of multiple types in it. We can access the elements of the tuple using std::get(), but std::get() always takes a constant variable parameter, so we can not simply iterate through it using a loop.

Are C++ tuples mutable?

Tuples are immutable. Lists are mutable. Tuples can contain different data types.


2 Answers

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;
 }
like image 92
max66 Avatar answered Oct 25 '22 08:10

max66


You cannot do this in namespace std under the standard. Injecting user-written code into std is only legal in very narrow circumstances, and this is not one of them.

You could put it in the global namespace, but then when outside the global namespace you won't be able to find it without a using ::operator+; or similar. Ie, a bad plan.

This leaves you with a few options. You could implement named operators. You could create a tag type, and state that tuples containing said tag type participate in your overload resolution. You could create a modified tuple type derived from std::tuple that has these operators.

I'll do the second here. This works because the lookup of operators follows both the namespace a type is in, and the namespace of all template parameters of the template type instance you are working on.

The other two options (derived type and named operator) can be done with similar implementations. This is c++14, as it makes the code shorter.

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))...
      );
    });
  }
}

where index_upto is:

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>{} );
  }
}

which is just a helper to do pack expansion without having to write another function at the point of pack expansion.

Live example

like image 23
Yakk - Adam Nevraumont Avatar answered Oct 25 '22 07:10

Yakk - Adam Nevraumont