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.
Arithmetic operationsIf addition and multiplication can be performed on the entries in a tuple, then addition and scalar multiplication on tuples can be defined.
Tuples in C++ A tuple is an object that can hold a number of elements. The elements can be of different data types.
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.
Tuples are immutable. Lists are mutable. Tuples can contain different data types.
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;
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With