Why not overload operator+=() for std::vector?
Asked Answered
C

5

30

I've started learning C++, so I don't know in my lack of knowledge/experience why something so seemingly simple to a rookie as what I'm about to describe isn't already in the STL. To add a vector to another vector you have to type this:

v1.insert(v1.end(), v2.begin(), v2.end());

I'm wondering whether in the real world people just overload the += operator to make this less verbose, for example to the effect of

template <typename T>
void operator+=(std::vector<T> &v1, const std::vector<T> &v2) {
    v1.insert(v1.end(), v2.begin(), v2.end());
}

so then you can

v1 += v2;

I also have this set up for push_back to "+=" a single element to the end. Is there some reason these things should not be done or are specifically avoided by people who are proficient in C++?

Chorale answered 16/6, 2011 at 1:38 Comment(15)
I would guess it's not in the standard because it's a bit awkward - the way you've done it the value types of the vectors have to be identical, whereas actually you could append any container (or range) of any type that's convertible to the value type on the LHS. And that's what insert does already. Also there are two plausible meanings for addition of vectors - concatenation as you've done here, or pairwise addition like valarray does, requiring operands of equal size. So it's not an ideal candidate for operator overloading.Su
Not to mention that from the title I assumed you meant to perform v[i] += v[i] over all the elements.Playsuit
Operator overloading makes sense in same particular cases, but frequently it leads to behavior that people find surprising. Behaviors that seem obvious to one don't seem so to another. Personally, I thought of appending when I saw the title of the question, and that's because I usually use Python, not C++.Darwindarwinian
IMHO, one reason to not think of operator+=() as pairwise addition is because the purpose of a vector is to contain elements. Operating on the elements is what I would consider beyond the design/responsibility of vector. Based on this then by principle of least privilege operator+=() should be assumed to be vector insertion.Chorale
@lukasz: Obviously you've never used MatLab (or octave).Langouste
@BenVoigt : I think that's quite an unfair comparison; despite the names of the data types, vector is a fundamentally different data type in C++ than Octave or Matlab -- the former implements the computer science definition of 'vector' (a one-dimensional array) while the latter implement the algebraic definition of 'vector' (a one-dimensional matrix). Because they're fundamentally different, they should be expected to behave differently and to be used differently.Reserpine
@ildjarn: Where do you think computer science got the word vector from in the first place? There's no fundamental difference. In fact, in MatLab a string is a vector of element type char. In many respects, computer science is algebra.Langouste
@BenVoigt : The fundamental difference is how one typically uses a vector in these two domains. The origin of the word is irrelevant.Reserpine
@ildjarn: You don't think anyone uses C++ for computation, storing numeric data in e.g. a vector<complex<double>>?Langouste
@BenVoigt : Sure, but they're the minority compared to the rest of C++ users.Reserpine
@ildjarn: And every single MatLab programmer falls into that minority (if they use C++ at all). Which directly implies my first comment.Langouste
@BenVoigt : And every single Matlab programmer who cares about performance knows to not use std::vector. This is really a pointless debate.Reserpine
@BenVoigt: I use Matlab for more than 60% of my work. I suppose one of the reasons I didn't stumble upon += as a pairwise element addition is because I'm not using vector for numerical data, but rather to store a list of objects which can't be added together. If I was going to use a container representing numerical elements I would probably use some kind of matrix library. However, I now understand that if you have something like vector<double> v1, v2; and later v1 += v2; it could get ambiguous.Chorale
@Lukasz: Then surely you would expect v1 + v2 to perform pairwise addition and return a new vector, right? += is supposed to be related to +, but do the addition in place.Langouste
@Lukasz: 5 years later... This is an example of "lifting". You want to lift the (+) operator into the vector container. It's a very nice thing to be able to do. In Haskell, zipwith would the function you're looking for. It takes two lists and zips them together with a function. You could also write zipwith in C++.Cedrickceevah
P
28

This is actually a case where I would like to see this functionality in the form of an overload of append(). operator+= is kinda ambiguous, do you mean to add the elements of each vector to each other? Or you mean to append?

However, like I said, I would have welcomed: v1.append(v2); It is clear and simple, I don't know why it isn't there.

Portulaca answered 16/6, 2011 at 2:3 Comment(6)
@Mark: I agree, if you wish for such a functionality, then do it yourself! template <typename L, typename R> void append(L& lhs, R const& rhs) { lhs.insert(lhs.end(), rhs.begin(), rhs.end()); } (with a bit of SFINAE to restrict L and R to containers).Quintessa
@Matthieu should it not be const R& lhs instead of R const& rhs?Chorale
@Lukasz: no! That's an awful anglicism! I prefer putting the qualifiers after the type, because then it's easier to interpret what a T const* is, the const applies to what is before it (here), so better be consistent!Quintessa
@Lukasz: The two are equivalent.Langouste
@MatthieuM. That append function would have to be called as append(a,b), not a.append(b), right?Jaunitajaunt
@baruch: yes, C++ does not support the concept of extension methods like C# does.Quintessa
S
8

I think the main reason is that the semantics of += aren't obvious. There's the meaning that you have here, but there's also an equally valid meaning, which is element-wise addition of each element of equal-sized vectors. Due to this ambiguity I assume they decided it was better to rely on the user to call insert diretly.

Sideling answered 16/6, 2011 at 2:27 Comment(2)
Also, as Steve Jessop said above, insert() is far more powerful as it operates on iterators. Why would you need anything else that's less useful?Dauntless
@Kerrek: More powerful != more useful. Often, simpler is better.Langouste
B
7

Operators should be overloaded only when you are not changing the meaning of those operators.*

What that means is, for example, if there is no mathematical definition of the product of two objects, don't overload the multiplication operator for those objects. If there was a mathematical correspondence, operators overloading can make a class more convenient to use by allowing equations to be expressed in the form a*b + c rather than the more verbose (a.multiply(b)).add(c) Where addition and multiplication operators are used, addition and multiplication should be the intent. Any other meaning is more than likely to confuse others. Some other types where overloading operators is acceptable (and, in my opinion, preferable) are smart pointers, iterators, complex numbers, vectors, and bignums.

This follows from one of the design goals of C++, that it should be possible to define classes that are as easy to use as built in types. Of course there are operators you can define on classes which are not mathematical concepts either. You may wish to overload the == and != operators instead of writing an isEqual method, and might want to overload = because the compiler's default assignment operator is not what you want.

On the other hand, overloading an operator to do something unexpected, like defining ^ to translate a string to Japanese, is obscure and dangerous. Your fellow programmers will not be happy to find out that what looked like an exclusive or comparison was actually something very different. The solution then is to write your classes to make it easy to write clear and maintainable code, whether that means using operator overloading or avoiding it.

Adding two vectors is too ambiguous to warrent defining an operator. As others have shown, many people have different ideas of what this means, whereas for a string it is universally understood that adding two strings together means concatenation. In your example, it isn't entirely clear whether you want to do a component wise add on all the elements, add an element to the end, or concat two vectors together. Although it may be more concise to do it that way, using operator overloading to create your own programming language isn't the best way to go.

*Yes, I know Stroustrup overloaded << and >> to do stream operations rather than bitshifts. But those weren't used often compared to arithmetic and pointer operators in the first place, and it could be argued that now that everyone knows how to use cout, it's generally understood that << and >> are the inserter and extractor operators. (He originally tried to use just < and > for input and output, but the meaning of those operators was so ingrained in everyone's minds that the code was unreadable.)

Bunghole answered 16/6, 2011 at 4:49 Comment(2)
I agree with your principle about obviousness. However, if I gave you a piece of paper and said "add this to the pile of papers", you would know just what I meant. And if we were pair programming together and you had just written the code to make (and set values in) some Foo object and I said "now you need to add it to the vector of Foos" I don't believe for a moment that you would think to increase each Foo in the vector by the one you had just made. You would call push_back or insert, right?Glazing
If you said "add this Foo to the vector of Foos" yes I would understand what you meant, and I would call push_back, otherwise you should've said "Add Foo to every Foo in this vector." If someone were to instead say "add these two vectors together" I would think they want me to do a component wise add. If concating the vectors was what you want, I would've expected you to say something like "put everything in this vector onto the end of that vector." But that's just the thing, I don't feel += by itself is enough information to disambiguate.Bunghole
G
4

In addition to what others mentioned about this syntax not being intuitive and therefore error prone, it also goes against a good design rule making general algorithms applied to various containers free functions, and container specific algorithms -- member functions. Most containers follow this rule, except std::string, which got a lot of flack from Herb Sutter for its monolithic design.

Gentilism answered 16/6, 2011 at 3:5 Comment(1)
Speaking of which, I never like it when operator+() is not commutative (e.g. string concatenation). Something about that just feels wrong...Cabaret
N
0

Posting a solution here. I use template to allow a being both lvalue or rvalue reference.

template <class T, class U, typename std::enable_if<std::is_same<typename std::remove_cv<typename std::remove_reference<U>::type>::type, std::vector<T>>::value, bool>::type = true>
U append(U && a, const std::vector<T> & b) {
  a.insert(a.end(), b.cbegin(), b.cend());
  return std::forward<U>(a);
}
Nina answered 11/2 at 4:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.