How can I deal with universal reference, when I want either to copy semantics with a function parameter const T&
or move semantics with function parameter T&&
. The later hides the first.
A sample code with algebraic vector operators follow.
#include <array>
#include <iostream>
template<typename T>
void neg(T &a) { for (auto &i : a) i = -i; }
// object 'a' remains available
template<typename T>
auto operator-(const T &a) { std::cout << "1\r\n"; T b = a; neg(b); return b; }
// object 'a' terminates its life
template<typename T>
auto operator-(T &&a) { std::cout << "2\r\n"; neg(a); return std::move(a); }
// make an rvalue
template<typename T1, typename T2>
auto operator (const T1 &a, const T2 &b) { return a; }
int main()
{
std::array<int, 4> a;
auto d = -(a a); // outputs 2 : correct
auto e = -a; // outputs 2 : I want 1
auto f = -std::move(a); // outputs 2 : correct
return 0;
}
EDIT: One of the solutions proposed by user17732522 (please upvote him).
template<typename T>
auto operator-(T &&a)
{
std::cout << "2\r\n"; neg(a); return std::move(a);
}
template<typename T>
auto operator-(T &a)
{
std::cout << "1\r\n";
std::remove_cvref_t<T> b = a;
neg(b); return b;
}
int main()
{
std::array<int, 4> a;
auto d = -(a a); // now outputs 2 : correct
auto e = -a; // now outputs 1 : correct
auto f = -std::move(a); // now outputs 2 : correct
const std::array<int, 4> b{1,2,3,4};
d = -(b b); // now outputs 2 : correct
e = -b; // now outputs 1 : correct
return 0;
}
Another solution, always based on user17732522 answer, is this:
template<typename T>
requires(!std::is_reference_v<T>)
auto operator-(T &&a);
template<typename T>
auto operator-(const T &a);
CodePudding user response:
You just need to remove const
from const T&
.
With using U = std::array<int, 4>;
:
The issue here is that T&&
deduces T
to U
, not const U
, since a
in main
isn't const
. And binding to U&
instead of const U&
is considered better in overload resolution.
If you remove const
from const T&
, then both candidates will after deduction have a function parameter U&
and consequently neither will be better based on that.
However, in partial ordering of function templates the T&
template will win over T&&
and so the former will be chosen if the function argument is a lvalue.
This does however have the consequence that a
in the function will not be const
qualified. You can obtain a const
reference from it by applying std::as_const
.
Alternatively you can use a requires
clause or std::enable_if
to constrain the T&&
template on T
being a non-reference type. Or you can use a single function template and decide in its body how to operate based on the type of the reference with if constexpr
. Relevant type traits: std::is_lvalue_reference_v<T>
or std::is_reference_v<T>
and std::is_lvalue_reference_v<decltype(a)>
.