4
votes

I was reading this stackoverflow answer where one reason why const T&& is not a universal (forwarding) reference is given:

Allowing const T&& to behave as forwarding references, would make it impossible to overload a template function who takes only an rvalue reference as parameter.

I do not know what this means. I guess it implies having two overloads of the same function (template), and one of them taking as a parameter const T&&. I also assume one of these overloads will always be called, while the other will never be called.

If my assumption is correct, what are the two overloaded functions? Or if I am wrong, what does the quoted paragraph actually mean?

Thank you.

1
Why do people answer in comments rather than in answers? - Christopher Pisz
@0x5453 You certainly are allowed to have const rvalue references - M.M
@0x5453 In the same answer that I linked to a use of const rvalue references is mentioned, therefore they are allowed. - user42768
It takes more effort to write a good answer than a comment as one has to ensure his answer is adequate (and complete enough) so he won't get downvotes. - Phil1970
@ChristopherPisz: Because comments can't get downvotes if they're wrong. Exhibit A being 0x5453's comment. - Nicol Bolas

1 Answers

3
votes

As far as I can see the section of answer that you have quoted is accurate but misleading.

First it's important to clarify that an rvalue reference and a forwarding reference are not the same thing, they just share the same notation &&. Whether this is a good thing is up for debate.

template <typename T>
void foo(T&&); // deduced type == forwarding reference

void foo(int&&); // explicit type == rvalue reference

Simple enough. So why isn't the following a forwarding reference?

template <typename T>
void foo(const T&&); // const rvalue reference despite deduced type

The best answer I can give you is 'because'. It appears to be a completely arbitrary decision by the Standards Committee. I can see no reason why const T&& can't be a a forwarding reference; it just isn't because the standard says so.

ยง14.8.2.1/ Deducing template arguments from a function call [temp.deduct.call]

A forwarding reference is an rvalue reference to a cv-unqualified template parameter.

Regardless of why this is the case, it becomes apparent that adding cv-qualification is the only way to tell the compiler to treat a deduced type as an rvalue reference rather than a forwarding reference. Which is the point made by your quote from the other answer.

Allowing const T&& to behave as forwarding references, would make it impossible to overload a template function who takes only an rvalue reference as parameter.

The reason I say this is misleading is because it implies that if we overload a a template to accept const T&& then this overload will be preferred for all rvalue references regardless of cv-qualification. This is not the case.

In the following code we can see that foo accepts const rvalue references but nothing else because it is not a forwarding reference.

struct Non_POD
{
    Non_POD(int i) : m_i(i) { }
    int m_i;
};

Non_POD foo() { return {0}; }

const Non_POD const_foo() { return {0}; }

template <typename T>
void bar(const T&& val)
{
    std::cout << "Accepts: const rvalue ref. ";
    if constexpr (std::is_rvalue_reference_v<decltype(val)>)
    {
        std::cout << "Val is rvalue reference.\n";
    }
    else if constexpr (std::is_lvalue_reference_v<decltype(val)>)
    {
        std::cout << "Val is lvalue reference.\n";
    }
    else
    {
        std::cout << "Val is lvalue.\n";
    }

    std::cout << std::endl;
}

int main()
{
    bar(foo());
    bar(const_foo());
    Non_POD x(0);
    //bar(x); // error
}

Expected Output (GCC 7.1)

Accepts: const rvalue ref. Val is rvalue reference.

Accepts: const rvalue ref. Val is rvalue reference.

This seems to support the quote, because it accepts const rvalue references and converts rvalue references into const rvalue references. However there's no overloading going on. If we introduce overloading we can see that it only accepts const rvalue references.

struct Non_POD
{
    Non_POD(int i) : m_i(i) { }
    int m_i;
};

Non_POD foo() { return {0}; }

const Non_POD const_foo() { return {0}; }

template <typename T>
void bar(const T&& val)
{
    std::cout << "Accepts: const rvalue ref. ";
    if constexpr (std::is_rvalue_reference_v<decltype(val)>)
    {
        std::cout << "Val is rvalue reference.\n";
    }
    else if constexpr (std::is_lvalue_reference_v<decltype(val)>)
    {
        std::cout << "Val is lvalue reference.\n";
    }
    else
    {
        std::cout << "Val is lvalue.\n";
    }

    std::cout << std::endl;
}

template <typename T>
void bar(T&& val)
{
    std::cout << "Accepts: forwarding ref. ";
    if constexpr (std::is_rvalue_reference_v<decltype(val)>)
    {
        std::cout << "Val is rvalue reference.\n";
    }
    else if constexpr (std::is_lvalue_reference_v<decltype(val)>)
    {
        std::cout << "Val is lvalue reference.\n";
    }
    else
    {
        std::cout << "Val is lvalue.\n";
    }

    std::cout << std::endl;
}

int main()
{
    Non_POD x(0);
    const Non_POD cx(0);

    bar(x);
    bar(cx);
    bar(Non_POD(0));
    bar(foo());
    bar(const_foo());
}

Expected Output (GCC 7.1)

Accepts: forwarding ref. Val is lvalue reference.

Accepts: forwarding ref. Val is lvalue reference.

Accepts: forwarding ref. Val is rvalue reference.

Accepts: forwarding ref. Val is rvalue reference.

Accepts: const rvalue ref. Val is rvalue reference.

We can see from the above that there is actually no way to declare a template that only accepts non const rvalue references.