This is an attempt to answer my own question. I would be grateful if anyone could comment on it weither they agree/do not agree with parts of my solution.
- ###The first argument of type
mat4should be passed by reference rather than by value inoperator+,operator*(scalar version) andoperator=signatures.
With the help of this reference let's carry out an analysis of the function operator+ (like it is done in this article) to understand why (analysis for operator* and operator= is similar).
Fist, we can assume two things on the code above :
- When the conditions for copy elision are met, the compiler will omit copy or move constructors of
mat4. I have tested this under GCC 6.1 and Clang 3.8 and it is indeed the case in all the situations I've tested when using the operators of this class. - For this class
mat4, that default constructor >= copy constructor = move constructor (the greater the faster in terms of execution speed). Becausestd::arrayis an aggregate, moving afloatis not any faster than copying it. Copy/move constructor probably use something likememcpyinternally and the default constructormemset(because of the callmatrix()inmat4's constructor) to set all elements to 0 (which I doubt would be slower thanmemcpyand may be even faster).
There are two cases to consider when calling operator+ :
- The first argument of type
mat4passed to the function is a lvalue. - It is a rvalue.
If the first argument is an lvalue, the pass-by-value (current) version of operator+ will call the copy constructor of mat4 to copy that object into lhs. Then when the function returns, it will call the move constructor of mat4 to move lhs into the object returned by operator+. The reason why it is the move and not the copy constructor that is called is explained in the reference given above. Here is the interesting extract :
(since C++11) In a return statement or a throw-expression, if the compiler cannot perform copy elision but the conditions for copy elision are met or would be met, except that the source is a function parameter, the compiler will attempt to use the move constructor even if the object is designated by an lvalue; see return statement for details.
However, if the argument passed to operator+ is an rvalue, copy of that value into lhs will be elided. In this case only the move constructor is called.
Now let's do the exact same analysis for the pass-by-reference version of operator+ which I propose the implementation to be the following :
constexpr const mat4 operator+(const mat4 &lhs, const mat4 &rhs) noexcept
{
mat4 result;
for (size_t i = 0; i < 16; ++i) {
result[i] = lhs[i] + rhs[i];
}
return result;
}
In both cases, i.e. when the first argument to the function is either an lvalue or an rvalue, calling the function will only result in a single call of the default constructor (mat4 result;). Note that the temporary object result will not be moved nor copied into the function's returned value because the conditions for copy elision are met.
Conclusion : this last solution is undeniably faster. Be careful, it is not everytime the case and only true here because of how operator+ can be implemented and because of the second assumption that default constructor >= copy constructor = move constructor (note that this conclusion would be also be true if default constructor = copy constructor = move constructor).
In operator=, it is unnecessary to use std::swap because you do unnecessary copies.
Just use this->matrix = other.matrix;. std::swap would only be interesting to use if the class implemented a resource (See here).
Until complete
constexprsupport forstd::array(C++17), you could create your own array wrapper and so you wouldn't have to write that ugly cast inoperator[]
Here is a wrapper inspired from std::array that could work :
template<class _Ty, size_t _Size>
struct arr
{
constexpr _Ty& operator[](size_t index) noexcept
{
return _arr[index];
}
constexpr const _Ty& operator[](size_t index) const noexcept
{
return _arr[index];
}
constexpr _Ty *data() noexcept
{
return _arr;
}
constexpr const _Ty *data() const noexcept
{
return _arr;
}
float _arr[_Size];
};