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 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)>.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With