What Is Use of the Ref-Qualifier 'Const &&'

What is use of the ref-qualifier `const &&`?

On the usefulness of const&&... (in general)

The usefulness of the const&& qualifier on the member method is minimal at best. The object cannot be modified in the same manner as a && method would allow it to be modified; it is const after all (as noted, mutable does change this). So we will not be able to rip out its guts, since the temporary is expiring anyway, as we would in something akin to a normal move.

In many ways the usefulness of the const&& may be best evaluated in the context of how useful an object of type const T&& is to begin with. How useful is a const T&& function argument? As pointed out in another answer (to this question) here, they are very useful in declaring functions deleted, e.g. in this case

template <class T> void ref (const T&&) = delete;

to explicitly disallow objects of prvalue and xvalue value category types from being used with the functions, and const T&& does bind to all prvalue and xvalue objects.

What is the usefulness of const&& method qualifier?

It is interesting to note that in the proposal C++ library extensions, optional, § 5.3, includes overloads, e.g.

constexpr T value() const &&;

that are qualified as const&& and are specified to perform the same action as the && alternative.

The reason I can infer for this case; is that this is for completeness and correctness. If the value() method is called on an rvalue, then it performs the same action independent of it being const or not. The const will need to be dealt with by the contained object being moved or the client code using it. If there is some mutable state with the object being contained, then that state can legitimately be changed.

There may well still be some merit in this; in no particular order...

  • To declare it = delete to prohibit the method's use on prvalues and xvalues.
  • If the type has mutable state and the qualifier makes sense (possibly in addition to the other qualifiers) in the target environment, consider it.
  • If you are implementing a generic container type, then for completeness and correctness, consider adding it and performing the same action as the && method. Advice here is sort from the standard library (and its extensions).

What would a "canonical" signature look like for a const&& qualified method?

Since the method will be performing the same action as the && method, I would advocate that the signature matches the && signature.

Const and reference member function qualifiers

Const and reference member function qualifiers are to be able to apply those qualifier to "this" as for regular parameter, so mainly, you have something like:

int getVarLRef(SomeClass& self) { return self.val; }
int getVarCLRef(const SomeClass& self) { return self.val; }

And there, I think you know that:

getVarCLRef(SomeClass()); // Valid, temporary can bind to const lvalue reference
getVarLRef(SomeClass()); // INVALID, temporary CANNOT bind to non-const lvalue reference

Const reference qualifier on a member function

The & is a ref-qualifier. Ref-qualifiers are new in C++11 and not yet supported in all compilers, so you don't see them that often currently. It specifies that this function can only be called on lvalues (and not on rvalues):

#include <iostream>

class kitten
{
private:
int mood = 0;

public:
void pet() &
{
mood += 1;
}
};

int main()
{
kitten cat{};
cat.pet(); // ok

kitten{}.pet(); // not ok: cannot pet a temporary kitten
}

Combined with the cv-qualifier const, it means that you can only call this member function on lvalues, and those may be const.

const& , & and && specifiers for member functions in C++

const& means, that this overload will be used only for const, non-const and lvalue object.

const A a = A();
*a;

& means, that this overload will be used only for non-const object.

A a;
*a;

&& means, that this overload will be used only for rvalue object.

*A();

for more information about this feature of C++11 standard you can read this post What is "rvalue reference for *this"?

Why do ref-qualifier together with cv-qualifier on operator overloading allow rvalue assignment?

Because rvalues could be bound to lvalue-reference to const. Just same as the following code:

foo& r1 = foo();       // invalid; rvalues can't be bound to lvalue-reference to non-const
const foo& r2 = foo(); // fine; rvalues can be bound to lvalue-reference to const

BTW: The overload qualified with rvalue-reference wins in overload resolution when calling on rvalues. That's why you mark it as delete explicitly works as expected.

Why this ref-qualified user defined convertion is called?

Because rvalues could be bound to lvalue-reference to const too. (They can't be bound to lvalue-reference to non-const.)

As the workaround, depending on your intent, you can remove the const qualifier as you said, or add overloading with rvalue-reference qualifier (and const qualifier if necessary as @HolyBlackCat suggested) and mark it as delete explicitly. E.g.

class B {
public:
operator A() const & {
return A{};
}
operator A() && = delete;
};

Use case for `&` ref-qualifier?

They are useful for both providing safety and optimizations.

For member functions returning a pointer to something they own (either directly or via view types like std::string_view or std::span), disabling the rvalue overloads can prevent errors:

struct foo {
int* take_ptr_bad() { return &x; }
int* take_ptr() & { return &x; }
int x;
};

foo get_foo();

void bar() {
auto ptr = get_foo().take_ptr_bad();
// ptr is dangling
auto ptr = get_foo().take_ptr();
// does not compile
}

The other is to provide some optimizations. For instance, you might overload a getter function to return an rvalue reference if this is an rvalue to prevent unnecessary copies:

struct foo {
const std::string& get_str() const & {
return s;
}

std::string&& get_str() && {
return std::move(s);
}

std::string s;
};

void sink(std::string);

foo get_foo();

void bar() {
sink(get_foo().get_str());
// moves the string only if the r-value overload is provided.
// otherwise a copy has to be made, even though the foo object
// and transitively the string is temporary.
}

These are how I use the feature, and I'm sure there are more use cases.

What is the purpose of Ref-qualified member functions ?

Just read down below:

During overload resolution, non-static cv-qualified member function of class X is treated as a function that takes an implicit parameter of type lvalue reference to cv-qualified X if it has no ref-qualifiers or if it has the lvalue ref-qualifier. Otherwise (if it has rvalue ref-qualifier), it is treated as a function taking an implicit parameter of type rvalue reference to cv-qualified X.

Example

#include <iostream>
struct S {
void f() & { std::cout << "lvalue\n"; }
void f() &&{ std::cout << "rvalue\n"; }
};

int main(){
S s;
s.f(); // prints "lvalue"
std::move(s).f(); // prints "rvalue"
S().f(); // prints "rvalue"
}

So during overload resolution the compiler looks for the function &-qualified if the caller object is an lvalue or for the function &&-qualified if the caller object is an rvalue.

Why is overloading on just one ref-qualifier not allowed?

It's not any different to the following situation:

struct S {};

void g(S s);
void g(S& s);

int main()
{
S s;
g(s); // ambiguous
}

Overload resolution has always worked this way; passing by reference is not preferred to passing by value (or vice versa).

(Overload resolution for ref-qualified functions works as if it were a normal function with an implicit first parameter whose argument is *this; lvalue-ref qualified is like a first parameter S &, const & is like S const & etc.)

I guess you are saying that g(s) should call g(S&) instead of being ambiguous.

I don't know the exact rationale, but overload resolution is complicated enough as it is without adding more special cases (especially ones that may silently compile to not what the coder intended).

As you note in your question, the problem can be easily avoided by using the two versions S & and S &&.



Related Topics



Leave a reply



Submit