5

There is templated vector class(it's about math, not container). I need to overload common math operations. Is there any sense to overload like this:

template <typename T, size_t D> Vector<T, D> operator+(const Vector<T, D>& left, const Vector<T, D>& right) { std::cout << "operator+(&, &)" << std::endl; Vector<T, D> result; for (size_t i = 0; i < D; ++i) result.data[i] = left.data[i] + right.data[i]; return result; } template <typename T, size_t D> Vector<T, D>&& operator+(const Vector<T, D>& left, Vector<T, D>&& right) { std::cout << "operator+(&, &&)" << std::endl; for (size_t i = 0; i < D; ++i) right.data[i] += left.data[i]; return std::move(right); } template <typename T, size_t D> Vector<T, D>&& operator+(Vector<T, D>&& left, const Vector<T, D>& right) { std::cout << "operator+(&&, &)" << std::endl; for (size_t i = 0; i < D; ++i) left.data[i] += right.data[i]; return std::move(left); } 

This works pretty fine with this test code:

auto v1 = math::Vector<int, 10>(1); auto v2 = math::Vector<int, 10>(7); auto v3 = v1 + v2; printVector(v3); auto v4 = v3 + math::Vector<int, 10>(2); printVector(v4); auto v5 = math::Vector<int, 10>(5) + v4; printVector(v5); // ambiguous overload // auto v6 = math::Vector<int, 10>(100) + math::Vector<int, 10>(99); 

and prints this:

operator+(&, &) 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, operator+(&, &&) 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, operator+(&&, &) 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 

There is problem with two rvalue references, but I think it doesn't matter.

Why I want to do that? Because of performance reason, in theory it would work little bit faster without creating new object, but will it? Maybe compilers optimize simple code with operator +(const Vector& left, const Vector& right) and there is no any reason for overload rvalue?

12
  • not really clear what is your question. If you want to know if there is any difference in what the compiler can optimize you can always look at its output, eg. here godbolt.org Commented Aug 8, 2017 at 20:24
  • && in template function in not the same as && in a non-template function. In a template function && is a forwarding reference (and reference collapsing rules apply); && in a non-template function is a rvalue reference and no collapsing is done. You have forwarding references. Commented Aug 8, 2017 at 20:25
  • 2
    @Richard: only if the parameters has the form T&&, in case of foo<T> (as is the case here) it is an actual r-value reference. Commented Aug 8, 2017 at 20:29
  • @DrewDormann, yes, I mentioned it Commented Aug 8, 2017 at 20:35
  • 2
    @devalone rvalue reference can refer to any object, not just temporaries. Also, if it is temporary data and the person write auto&& result = x + temporary(); then the result is a dangling reference since it refers to the temporary that is now dead. Commented Aug 8, 2017 at 21:54

2 Answers 2

3

It depends on your implementation of Vector:

  • If the class is faster to move than to copy, providing the additional overloads for moves could result in performance improvements.
  • Otherwise, it should not be faster to provide the overloads.

In a comment, you mentioned that Vector looks like this:

template <typename T, size_t D> class Vector { T data[D]; // ... }; 

From your code, I also assume that T is a simple arithmetic type (e.g., float, int) where copying is as fast as moving it. In that case, you cannot implement a move operation for Vector<float, D> which will be faster than a copy operation.

To make move operations faster than copying, you could change the representation of your Vector class. Instead of storing a C-array, you could store a pointer to the data, which allows a much more efficient move operation if the size D is large.

As an analogy, your current Vector implementation is like an std::array<T, D> (which holds internally a c-array and needs to be copied), but you could switch to an std::vector<T> (which holds pointers to the heap and is easy to move). The larger the value D becomes, the more attractive should it be to switch from std::array to std::vector.

Let us look closer at the differences when providing overloads for move operations.

Improvement: in-place updates

The advantage of your overloads is that you can use in-place updates to avoid having to create a copy for the result as you have to do in your operator+(&,&) implementation:

template <typename T, size_t D> Vector<T, D> operator+(const Vector<T, D>& left, const Vector<T, D>& right) { std::cout << "operator+(&, &)" << std::endl; Vector<T, D> result; for (size_t i = 0; i < D; ++i) result.data[i] = left.data[i] + right.data[i]; return result; } 

In your overloaded version, you can update in-place:

template <typename T, size_t D> Vector<T, D>&& operator+(const Vector<T, D>& left, Vector<T, D>&& right) { std::cout << "operator+(&, &&)" << std::endl; for (size_t i = 0; i < D; ++i) right.data[i] += left.data[i]; return std::move(right); } 

However, moving the result will result in a copy when you use your current implementation of Vector, whereas in the non-overloaded version, the compiler could get rid of it using return-value optimization. If you use a std::vector like representation, moving is fast, so the in-place update version should be faster than the original version (operator+(&,&)).

Can a compiler do the in-place update optimization automatically?

Highly unlikely that a compiler will be able to do it without help.

In the non-overloaded version, the compiler sees two arrays which are constant references. It will most likely be able to perform return-value optimization, but knowing that it can reuse one of the existing objects, requires lots of extra knowledge that the compiler does not have at that point.

Summary

Providing overloads for rvalues is reasonable from a pure performance perspective if Vector is faster to move than to copy. If moving is not faster, then there is no gain in providing the overloads.

Sign up to request clarification or add additional context in comments.

1 Comment

Move of array can't be slower than copy, so it shouldn't make program slower. Reason why I wanted to use overload with rvalue is to prevent of creating additional object in function, but as you mentioned, there is return value optimization, but when I tried to compile in gcc with -O4, optimization wan't maken.
2

If your vector is cheaper to move than to copy (usually the case when internally it stores a pointer to data which can be cheaply copied) than having overloads for rvlaue references would help performance. You can look at std::string and how it has overloads for all kind of possible references in it's two arguments here: http://en.cppreference.com/w/cpp/string/basic_string/operator%2B

EDIT

Since OP clarifies that internal data representation is a c-style array, there is no benefit of providing multiple overloads. C-style arrays can not be moved cheaply - they are copied instead - so those multiple overloads serve no purpose.

2 Comments

My vector is just simple array defined like this T data[D]; and it will be used only with simple types like int, double, etc and I think size of vector won't be bigger than 4.
Reason why I wanted to use rvalue overload is to prevent creating of additional object.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.