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);
You just need to remove
const
fromconst T&
.With
using U = std::array<int, 4>;
:The issue here is that
T&&
deducesT
toU
, notconst U
, sincea
inmain
isn'tconst
. And binding toU&
instead ofconst U&
is considered better in overload resolution.If you remove
const
fromconst T&
, then both candidates will after deduction have a function parameterU&
and consequently neither will be better based on that.However, in partial ordering of function templates the
T&
template will win overT&&
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 beconst
qualified. You can obtain aconst
reference from it by applyingstd::as_const
.Alternatively you can use a
requires
clause orstd::enable_if
to constrain theT&&
template onT
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 withif constexpr
. Relevant type traits:std::is_lvalue_reference_v<T>
orstd::is_reference_v<T>
andstd::is_lvalue_reference_v<decltype(a)>
.