12
votes

I tested the following code with GCC, Clang, ICC and VS:

void f() {}
  
void g(void (&&)()) { }

int main() {
    g(f);
}

As we can see, g takes an rvalue reference but f is an lvalue and, in general, rvalue references cannot be bound to lvalues. That's exactly what ICC complains about:

error: an rvalue reference cannot be bound to an lvalue

VS also gives an error but for another reason:

error C2664: 'void h(void (__cdecl &&)(void))' : cannot convert parameter 1 from 'void (__cdecl *)(void)' to 'void (__cdecl &&)(void)'

This suggests to me that VS is immediately performing a function-to-pointer conversion rather than directly bind the reference to f. It's worth mentioning that if I replace g(f) with g(&f) then the four compilers yield this very same error.

Finally, GCC and Clang accept the code and I believe they are correct. My reasoning is based on 8.5.3/5

A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as

— If the reference is an lvalue reference [...]

— Otherwise, [...] the reference shall be an rvalue reference.

     — If the initializer expression is a [...] function lvalue [...]

     then the reference is bound to the value of the initializer expression [...]

Is my interpretation correct (that is, Clang and GCC are compliant for the given reason)?

1

1 Answers

9
votes

Is my interpretation correct [...]?

Yes.

Your interpretation is correct because of the Paragraph of the Standard that you quoted. A further confirmation comes from Paragraph 13.3.3.1.4/3 on reference binding:

Except for an implicit object parameter, for which see 13.3.1, a standard conversion sequence cannot be formed if it requires binding an lvalue reference other than a reference to a non-volatile const type to an rvalue or binding an rvalue reference to an lvalue other than a function lvalue. [...]

Paragraph 13.3.3.2/3 contains a further (indirect) confirmation:

[...] Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if

— [...]

— S1 and S2 are reference bindings (8.5.3) and S1 binds an lvalue reference to a function lvalue and S2 binds an rvalue reference to a function lvalue. [ Example:

int f(void(&)()); // #1
int f(void(&&)()); // #2
void g();
int i1 = f(g); // calls #1

end example ]