34

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<int,int> a = {1,2};
std::tuple<int,int> b = {2,4};


std::tuple<int,int> c = a + b; // possible syntax 1
a += b; // possible syntax 2
a += {2,4}; // possible syntax 3

Where the output tuple would have the value {3,6}

Was looking at CPP reference, but I couldn't find this functionality. It's possible that this and this question are relevant, however the answers are obfuscated by other complexities.

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
Prunus Persica
  • 1,173
  • 9
  • 27

4 Answers4

29

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;
}
Vishaal Shankar
  • 1,648
  • 14
  • 26
  • 12
    Not often do you see `std::valarray` nowadays. Upvoted. The obvious downside is that it's dynamic which is the opposite of `std::tuple`. – DeiDei Jun 12 '18 at 10:44
  • 3
    This seems like a good solution. The downside is that if we have `std::valarray d{2,4,3};`, then `a -= d; // a is {1, 2}` and `d -= b; // d is {3, 0, 0}` which is behavior that some might find counter-intuitive – Prunus Persica Jun 12 '18 at 12:24
  • 1
    You are right. It works well when both valarray's are of the same size. It could be worse when `d -= b` because `d` will have a size of 3 and the third value might end up being garbage. – Vishaal Shankar Jun 12 '18 at 14:26
11

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.

Simon Kraemer
  • 5,700
  • 1
  • 19
  • 49
  • 2
    Nice, but for my understanding: why didn't you do `std::tuple operator+ (std::tuple lhs, const std::tuple& rhs) { return lhs += rhs; }`? – JHBonarius Jun 12 '18 at 11:44
  • 1
    See [this question](https://stackoverflow.com/questions/46028709/why-is-passing-by-value-if-a-copy-is-needed-recommended-in-c11-if-a-const-re) for why it is better to pass by value if you need to work with a copy. – KorbenDose Jun 12 '18 at 12:02
  • 1
    @JHBonarius Because this way I didn't need to deal with return values in the helper function. Implementing operator+= was just easier.... – Simon Kraemer Jun 12 '18 at 12:06
  • 1
    If you are using c++17, consider `[[maybe_unused]] auto l` to suppress the unused warning. For further information see the [attributes page on cppreference](http://en.cppreference.com/w/cpp/language/attributes) – KorbenDose Jun 12 '18 at 12:07
  • @KorbenDose I don't see how this applies here. – Simon Kraemer Jun 12 '18 at 12:07
  • @KorbenDose I didn't know about `[[maybe_unused]]` yet. Thank you. – Simon Kraemer Jun 12 '18 at 12:08
  • @SimonKraemer If I'm not mistaken, JHBonarius and I were saying the same thing. Passing the `lhs` argument by value for the `operator+` has no effect on the `operator+=`, you can still implement everything the same way. What you achieve is not having to separatley copy `lhs` into `copy`. – KorbenDose Jun 12 '18 at 12:13
9

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.

DeiDei
  • 10,205
  • 6
  • 55
  • 80
  • 3
    Maybe consider specifying the parameters for `operator+` as tuples, so it doesn't clash with other generic `operator+` definitions. `template constexpr auto operator+(const std::tuple& t1, const std::tuple& t2)`. That way you also don't need to assert on the same size of both tuples. – KorbenDose Jun 12 '18 at 11:11
  • 1
    @KorbenDose Thank you for the suggestion. Edited. I although need the assert, as I would like to have e.g. `std::tuple` and `std::tuple` be addable, which requires separate typename parameters. – DeiDei Jun 12 '18 at 12:17
  • I see, very interesting thought. I like it that way even better! – KorbenDose Jun 12 '18 at 18:35
3

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

lubgr
  • 37,368
  • 3
  • 66
  • 117
  • This does answer the question, however do you have any thoughts on how this might work for arbitrary length (and type) tuples? – Prunus Persica Jun 12 '18 at 12:44
  • @PrunusPersica see [DeiDei's answer](https://stackoverflow.com/a/50815143/9593596), this solution is valid for any tuples of equal length. – lubgr Jun 12 '18 at 13:01