In a member function of a class template, is T&& an rvalue reference or a forwarding reference?
Asked Answered
S

2

8

I know that for the following function

template <typename T>
void do_something(T&& arg);

the function parameter is a forwarding reference. But in the following case, is it still a forwarding reference or an rvalue reference?

template <typename T>
class MyClass
{
    void do_something(T&& arg);
};

I think it is still a forwarding reference, but I'm not sure. Furthermore, I'd like to know what can be done to enforce an rvalue reference or a forwarding reference, if the result is not what I intended.

Scirrhous answered 14/9, 2017 at 8:8 Comment(1)
Possible duplicate: When is a reference a forwarding reference, and when is it an rvalue reference?Sepulchral
D
16

It's an rvalue reference. Forwarding references can only appear in a deduced context. This is just a member function that accepts an rvalue reference to the class template parameter.

You can't force a forwarding reference to be an rvalue reference if you want to maintain template argument deduction for functions. If you don't mind specifying the template argument all over the place, then this will always and only ever give an rvalue reference:

template<typename T> struct identity { using type = T; };

template<typename T> void func(typename identity<T>::type&&);

In retrospect, there actually is a way to maintain deduction but force only rvalue refs to be accepted (besides the self documenting one in Simple's answer). You can provide a deleted lvalue overload:

template<typename T>
void func(T&) = delete;

template<typename T>
void func(T&& s)
{
    // ...
}

The lvalue overload is more specialized when passed an lvalue. And on account of being deleted, will give a somewhat clear error message.

Downstairs answered 14/9, 2017 at 8:9 Comment(5)
Does decuced context mean that the type is automatically deduced from the passed argument? If I understand you correctly, T is not deduced, because it's part of the class signature.Scirrhous
@MartinKalbfuß - Precisely. A deduced context is when you call the free function version of do_something.Downstairs
A std::is_rvalue_reference_v< decltype(arg) > constraint in C++20 will do the job as well. decltype(arg) will invariably be a reference while the template parameter type T will be a value type when the argument passed to do_something is an rvalue.Scorper
@Scorper - Why not post it as an answer? I think having multiple diverse answers works better for SO than having all options in one answer.Downstairs
@StoryTeller-UnslanderMonica it is just a comment to your In retrospect section, which provides an alternative to constrain forwarding references to be lvalue or rvalue references. The OP's template class snippet does not contain forwarding references.Scorper
B
7

Furthermore I like to know, what can be done to enforce an rvalue reference

If you always want an rvalue reference in a deduced context (and not a forwarding reference), then you can use this:

template<
    typename T,
    typename = std::enable_if_t<!std::is_lvalue_reference<T>::value>
>
using rval_ref = T&&;

template<typename T>
void foo(rval_ref<T> s)
{
    // ...
}

foo can only be called with an rvalue, and T will not be a reference (i.e. if you call foo with std::string&&, then T will be std::string).

Birth answered 14/9, 2017 at 8:30 Comment(1)
Not bad. Got rid of the dependent type and used SFINAE to block lvalue refs. +1Downstairs

© 2022 - 2024 — McMap. All rights reserved.