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
constfromconst T&.With
using U = std::array<int, 4>;:The issue here is that
T&&deducesTtoU, notconst U, sinceainmainisn'tconst. And binding toU&instead ofconst U&is considered better in overload resolution.If you remove
constfromconst 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
ain the function will not beconstqualified. You can obtain aconstreference from it by applyingstd::as_const.Alternatively you can use a
requiresclause orstd::enable_ifto constrain theT&&template onTbeing 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)>.