3

I ran up on some code

template<class InputIt, class T>
constexpr // since C++20
T accumulate(InputIt first, InputIt last, T init)
{
    for (; first != last; ++first) {
        init = std::move(init) + *first; // std::move since C++20
    }
    return init;
}

I have a question. Why would we have to use std::move on init even if init is int?

Bob
  • 1,433
  • 1
  • 16
  • 36
  • you don't "have to" for any type. – Mooing Duck Apr 16 '20 at 20:26
  • The point is in generic code you don't *know* what `T` will be. It could be `int`, or it could be `MyBigFatObject`. – 0x5453 Apr 16 '20 at 20:26
  • The only advantage I see here is if there is a `T operator+(T&&, const T&)` which would be surprising. But I see that there is a note that `std::move` was added in C++20 so there is probably an upcoming change to the language which would make that case more common. I'm curious why `init += *first` wouldn't be used intead. – François Andrieux Apr 16 '20 at 20:27
  • @FrançoisAndrieux: There's a huge advantage if `init` is a string with a large enough buffer, as this changes from `N` allocations/copies to 0. Also, `std::move` was added in C++11. – Mooing Duck Apr 16 '20 at 20:29
  • 3
    @MooingDuck `std::move` was added *to that function* in C++20. It's noted in the linked example. And using `init += *first` would have the same benefit. Though I see that `operator+` with rvalue reference does exist for `std::string`, which I didn't know about. – François Andrieux Apr 16 '20 at 20:30

3 Answers3

5

You're right, moving an int is no different from copying it.

Here, std::move only becomes useful only if T's overloaded operator+ behaves differently for lvalues and rvalues.

I've never heard of such classes, but I guess it could be useful for dynamic arrays that overload + in a clever way:

struct Vec
{
    std::vector<int> elems;
};

// Returns a completely new vector.
Vec operator+(const Vec &a, const Vec &b)
{
    assert(a.size() == b.size());
    Vec ret(a.size());
    for (std::size_t i = 0; i < a.size(); i++)
        ret.elems[i] = a.elems[i] + b.elems[i];
    return ret;
}
// Reuses storage of `a`.
Vec operator+(Vec &&a, const Vec &b)
{
    assert(a.size() == b.size());
    for (std::size_t i = 0; i < a.size(); i++)
        a.elems[i] += b.elems[i];
    return std::move(a);
}
// Reuses storage of `b`.
Vec operator+(const Vec &a, Vec &&b)
{
    return std::move(b) + a;
}
// Reuses storage of `a`.
Vec operator+(Vec &&a, Vec &&b)
{
    return std::move(a) + b;
}

Edit: apparently std::string does a similar thing: its + reuses storage of one of the operands, if possible. (Thanks @FrançoisAndrieux and @Artyer.)

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 5
    [`std::operator+(std::string&&, const std::string&)`](https://en.cppreference.com/w/cpp/string/basic_string/operator%2B) exists. In general, I would assume it would be implemented as `return std::move(lhs += rhs)` – Artyer Apr 16 '20 at 20:36
3

There's no "need," per se, to use std::move in the case where T is int. However, since we can think of std::move as basically meaning "please cast this expression to an rvalue," a good compiler should not introduce any overhead when compiling this code in the case where T is an int. So in that sense, this code will definitely improve performance for some types T, and it's unlikely to hurt performance on types for which moving is a no-op.

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
1

We don't "have" to std::move init. We (or rather, the standard library in this instance) do it because moving can be more efficient than copying for some types.

even if init is int?

init is not necessarily int. It is T, which is a template type argument.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • `std::move` only helps if you have `T operator+(T&&, const T&)` which I have never encountered before. Is there anything like that in the standard library, or is there another reason for it that I don't see? Otherwise, while it isn't wrong, it seems unlikely to ever be relevant. Using `init += *first` seems more likely to do the right thing than `init = std::move(init) + *first;` to me. – François Andrieux Apr 16 '20 at 20:29
  • But how is it going under the hood? Could you explain please? – Bob Apr 16 '20 at 20:29
  • 2
    @FrançoisAndrieux: `std::string` has the overload. https://en.cppreference.com/w/cpp/string/basic_string/operator%2B – Mooing Duck Apr 16 '20 at 20:31
  • @MooingDuck Thank you for the example. – François Andrieux Apr 16 '20 at 20:31
  • @FrançoisAndrieux `std::accumulate` is implemented in terms of `operator+`, so it can't use `+=` – Artyer Apr 16 '20 at 20:38
  • 1
    @Artyer In retrospect that seems obvious. Thank you for the clarifiation. – François Andrieux Apr 16 '20 at 20:38