Search code examples
c++forwarding-reference

I want to move semantics but I get a universal reference which hides copy semantics


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);

Solution

  • 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)>.