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
Iter_Swap() Versus Swap() -- What's the Difference
Exclude Source File in Compilation Using Makefile
How to Encode a String to Base64 Using Only Boost
C++ Integer->Std::String Conversion. Simple Function
Sort Based on Multiple Things in C++
How to Use Memcpy in C++ to Copy Classes That Have No Pointers or Virtual Functions
How to Erase & Delete Pointers to Objects Stored in a Vector
C++ Handling of Excess Precision
How to Pack a Visual Studio C++ Project for Release
Why Is (Void) 0 a No Operation in C and C++
Cannot Convert Parameter 1 from 'Char' to 'Lpcwstr'
Variable Length Arrays (Vla) in C and C++
How to Use the _Attribute_((Visibility("Default")))
Manual for Cross-Compiling a C++ Application from Linux to Windows
C++ Streams Confusion: Istreambuf_Iterator VS Istream_Iterator
Authenticating Users Using Active Directory in Client-Server Application
Is Is a Good Practice to Put the Definition of C++ Classes into the Header File