What Is the Purpose of Ref-Qualified Member Functions

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.

LValue ref qualified member function being called on an RValue object

Okay, so the thing to note here is that overload resolution only ever considers one conversion function for i. They don't both participate, and so the reference qualifier cannot be used to differentiate them. For the case of binding a reference

[over.match.ref]

Under the conditions specified in [dcl.init.ref], a reference can be
bound directly to the result of applying a conversion function to an
initializer expression. Overload resolution is used to select the
conversion function to be invoked. Assuming that “reference to cv1 T”
is the type of the reference being initialized, and “cv S” is the type
of the initializer expression, with S a class type, the candidate
functions are selected as follows:

  • The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S
    and yield type “lvalue reference to cv2 T2” (when initializing an
    lvalue reference
    or an rvalue reference to function) or “cv2 T2” or
    “rvalue reference to cv2 T2” (when initializing an rvalue reference or
    an lvalue reference to function), where “cv1 T” is
    reference-compatible with “cv2 T2”, are candidate functions. For
    direct-initialization, those explicit conversion functions that are
    not hidden within S and yield type “lvalue reference to cv2 T2” (when
    initializing an lvalue reference
    or an rvalue reference to function)
    or “rvalue reference to cv2 T2” (when initializing an rvalue reference
    or an lvalue reference to function), where T2 is the same type as T or
    can be converted to type T with a qualification conversion, are also
    candidate functions.

According to the text in bold, when initializing i, our only candidate is operator int const&. So overload resolution can either pass here, or fail entirely. But it cannot select operator int, since that one is not even under consideration. It succeeds because a const qualified lvalue reference can bind to the object argument.

On the other hand, for initializing a value

[over.match.conv]

Under the conditions specified in [dcl.init], as part of an
initialization of an object of non-class type, a conversion function
can be invoked to convert an initializer expression of class type to
the type of the object being initialized. Overload resolution is used
to select the conversion function to be invoked. Assuming that “cv1 T”
is the type of the object being initialized, and “cv S” is the type of
the initializer expression, with S a class type, the candidate
functions are selected as follows:

  • The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S
    and yield type T or a type that can be converted to type T via a
    standard conversion sequence are candidate functions. For
    direct-initialization, those explicit conversion functions that are
    not hidden within S and yield type T or a type that can be converted
    to type T with a qualification conversion are also candidate
    functions. Conversion functions that return a cv-qualified type are
    considered to yield the cv-unqualified version of that type for this
    process of selecting candidate functions. A call to a conversion
    function returning “reference to X” is a glvalue of type X, and such a
    conversion function is therefore considered to yield X for this
    process of selecting candidate functions.

So when initializing j both conversion functions participate as overloads, and here the reference qualifier makes a difference.

You do get a dangling reference here, and it seems to be due to a dark corner in the language. The bullet in the first quoted paragraph could probably be refined to consider the binding of const lvlaue references better. Since those may bind to temporaries as well, your second conversion operator could ideally be a candidate under better rules.

Difference between l-value ref-qualified member function and unqualified member function?

They are different.

Foo foo;
std::move(foo).func();

will call func() but not func()&.

Similarly:

Foo make_foo() { return {}; }
make_foo().func();

will only work on the void func() signature, not the void func()& signature.

void func()& means that it only is a valid call on lvalues, not rvalues.

Returning by value or by rvalue reference from rvalue reference qualified member function?

values is an object member and an lvalue, so if you just return values directly, it will be copied to the return value, not moved. The point of the && ref-qualified overload is to avoid making an unnecessary copy. return std::move(values) accomplishes this by casting values to an rvalue, so that it gets moved from instead of copied.

For the second part of your question: both have their advantages and disadvantages. As the answer you linked notes, returning by value from the && overload avoids lifetime issues, since the returned object will have its lifetime extended if a reference is immediately bound to it. On the other hand, returning by value could destroy the value of values unexpectedly. For instance:

DataType Widget::data() &&
{ return std::move(values); }

void func() {
Widget foo;
std::move(foo).data(); // Moves-constructs a temporary from
// foo::value, which is then immediately
// destroyed.
auto bar = foo.data(); // Oops, foo::value was already moved from
// above and its value is likely gone.
}

Wording of GCC's error message when calling lvalue-ref qualified member function on temporary object

This is just a case of a suboptimally worded error message in GCC.

My guess would be that in GCC's implementation, diagnosing a mismatch between differing ref-qualifiers shares some code with diagnosing a mismatch between const and non-const, and they reused the same error message.

I would encourage you to file a GCC bug suggesting an improvement to the error message.

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;
};

ref-qualified member functions as template arguments?

According to 8.3.5 [dcl.fct] paragraph 6 (I added some highlightening to the quoted text):

The return type, the parameter-type-list, the ref-qualifier, and the cv-qualifier-seq, but not the default arguments (8.3.6) or the exception specification (15.4), are part of the function type.

That is, the ref-qualifier is certainly part of the type. Further according to 8.4.1 [dcl.fct.def.general] paragraph 5 you can create pointer-to-members including the ref-qualifiers:

A cv-qualifier-seq or a ref-qualifier (or both) can be part of a non-static member function declaration, non-static member function definition, or pointer to member function only (8.3.5); see 9.3.2.

There is no specific restriction that pointer to member functions with ref-qualifier cannot be used as non-type template arguments. That is, I think the partial specialization you tried to use should work. However, support for ref-qualifiers is a fairly new feature in both clang and gcc, i.e., probably not all corner cases have been ironed out. I tried the snipped above with fairly recent snapshots of both gcc (20130811) and clang (trunk 190769) and both compiled the code OK. Of course, this snippet doesn't really do anything and I didn't try to abuse this feature. I would guess you just triggered a few compiler bugs and I'm sure that both projects would appreciate error reports against their latest snapshots.



Related Topics



Leave a reply



Submit