5

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.

user42768
  • 1,951
  • 11
  • 22
  • Why do people answer in comments rather than in answers? – Christopher Pisz Jul 02 '18 at 21:53
  • 2
    @0x5453 You certainly are allowed to have const rvalue references – M.M Jul 02 '18 at 21:57
  • 2
    @0x5453 In the same answer that I linked to a use of const rvalue references is mentioned, therefore they are allowed. – user42768 Jul 02 '18 at 22:03
  • @0x5453 Please write answers as answers, not comments! – Nic Jul 02 '18 at 22:25
  • 1
    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 Jul 02 '18 at 22:31
  • @Phil1970 Indeed, but maybe that's the point. – ravnsgaard Jul 02 '18 at 23:21
  • 1
    @ChristopherPisz: Because comments can't get downvotes if they're wrong. Exhibit A being 0x5453's comment. – Nicol Bolas Jul 02 '18 at 23:34
  • What if the quoted answer tries to say that if both `T&&` and `const T&&` would be universal references, it would be impossible to define a function that would take an rvalue of any type (but only rvalue, not also lvalues) because if both versions were universal references then lvalues would be accepted as well? Therefore, given that `const T&&` is not a universal reference. a function `template f(const T&&)` would accept any types of rvalues (but no lvalues). – user42768 Jul 03 '18 at 16:42

1 Answers1

4

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 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 bar accepts const rvalue references but nothing else because val 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 bar 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 bar 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.

Fibbs
  • 1,350
  • 1
  • 13
  • 23
  • I arrived at the same conclusion in the last comment I wrote on my question: "What if the quoted answer tries to say that if both T&& and const T&& would be universal references, it would be impossible to define a function that would take an rvalue of any type (but only rvalue, not also lvalues) because if both versions were universal references then lvalues would be accepted as well? Therefore, given that const T&& is not a universal reference. a function template f(const T&&) would accept any types of rvalues (but no lvalues).". This is what you are saying, right? – user42768 Jul 03 '18 at 17:58
  • This is what I'm saying. But if you are overloading be wary that declaring an overload that accepts a universal/forwarding reference will cause `const T&&` to *only* accept const rvalue references. In the unlikely event that you want one overload for rvalue references (const and non const) and another with perfect forwarding for everything else then you can't do it with the current syntax. – Fibbs Jul 03 '18 at 18:08
  • 1
    Yes, I understand. I don't know why in the answer that I quoted the term "overloading" is used. Maybe the author was trying to say that using a template function (just one overload) is somehow similar to "overloading" that function (not using the literal meaning of C++ overloading). – user42768 Jul 03 '18 at 18:14
  • Yeh I don't know why he's used it either. Glad to help. – Fibbs Jul 03 '18 at 19:01