I have some values held in a tuple, and I am looking to add another tuple to it element-wise. So I would like functionality like this:
std::tuple
The solution by @lubgr is satisfactory for your specific use-case. I offer you a generic solution which will work for tuples of different types, as well tuples of different (equal) sizes.
#include <tuple>
#include <utility>
#include <iostream>
template<typename... T1, typename... T2, std::size_t... I>
constexpr auto add(const std::tuple<T1...>& t1, const std::tuple<T2...>& t2,
std::index_sequence<I...>)
{
return std::tuple{ std::get<I>(t1) + std::get<I>(t2)... };
}
template<typename... T1, typename... T2>
constexpr auto operator+(const std::tuple<T1...>& t1, const std::tuple<T2...>& t2)
{
// make sure both tuples have the same size
static_assert(sizeof...(T1) == sizeof...(T2));
return add(t1, t2, std::make_index_sequence<sizeof...(T1)>{});
}
I used a few C++17 features (mainly template-related) without which the code would become a bit more complicated. A possible improvement would be to make use of move-semantics.
Obviously, this applies for the first "possible syntax" you provided.
You could also consider using std::valarray since it allows exactly the things that you seem to want.
#include <valarray>
int main()
{
std::valarray<int> a{ 1, 2 }, b{ 2, 4 }, c;
c = a - b; // c is {-1,-2}
a += b; // a is {3,6}
a -= b; // a is {1,2} again
a += {2, 4}; // a is {3,6} again
return 0;
}
Here is an operator definition for syntax #1:
template <class S, class T> std::tuple<S, T> operator + (const std::tuple<S, T>& lhs, const std::tuple<S, T>& rhs)
{
return std::make_tuple(std::get<0>(lhs) + std::get<0>(rhs), std::get<1>(lhs) + std::get<1>(rhs));
}
Syntax #2 and #3 are not possible without creating a custom struct, because they can only be defined as members of the classes they operate on (and you can't touch existing classes in namespace std
).
You could use something like this, which supports all three of your syntax proposals:
#include <tuple>
#include <utility>
namespace internal
{
//see: https://stackoverflow.com/a/16387374/4181011
template<typename T, size_t... Is>
void add_rhs_to_lhs(T& t1, const T& t2, std::integer_sequence<size_t, Is...>)
{
auto l = { (std::get<Is>(t1) += std::get<Is>(t2), 0)... };
(void)l; // prevent unused warning
}
}
template <typename...T>
std::tuple<T...>& operator += (std::tuple<T...>& lhs, const std::tuple<T...>& rhs)
{
internal::add_rhs_to_lhs(lhs, rhs, std::index_sequence_for<T...>{});
return lhs;
}
template <typename...T>
std::tuple<T...> operator + (std::tuple<T...> lhs, const std::tuple<T...>& rhs)
{
return lhs += rhs;
}
Working example:
http://coliru.stacked-crooked.com/a/27b8cf370d44d3d5
http://coliru.stacked-crooked.com/a/ff24dae1c336b937
I would still go with named structs in most cases. Tuples are seldom the correct choice.