Compiler thinks that "A(A&)" accepts rvalues for a moment?
Asked Answered
R

1

21

I have this code

struct A { A(); A(A&); }; 
struct B { B(const A&); }; 

void f(A); 
void f(B); 

int main() { 
   f(A()); 
}

To my surprise this fails with GCC and Clang. Clang says for example

Compilation finished with errors:
source.cpp:8:10: error: no matching constructor for initialization of 'A'
       f(A()); 
         ^~~
source.cpp:1:21: note: candidate constructor not viable: expects an l-value for 1st argument
    struct A { A(); A(A&); }; 
                    ^
source.cpp:1:16: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
    struct A { A(); A(A&); }; 
               ^
source.cpp:4:13: note: passing argument to parameter here
    void f(A); 

Why do they choose the first f, when the second f works fine? If I remove the first f, then the call succeeds. What is more weird to me, if I use brace initialization, it also works fine

int main() { 
   f({A()}); 
}

They all call the second f.

Redbreast answered 6/4, 2013 at 15:10 Comment(0)
C
17

It's a language quirk. The first f matches better because your A requires no conversion to match the argument type (A), but when the compiler attempts to make the call the fact that no suitable copy constructor can be found causes the call to fail. The language doesn't allow taking account of the feasibility of the actual call into consideration when performing the overload resolution step.

Closest matching standard quote ISO/IEC 14882:2011 13.3.3.1.2 User-defined conversion sequences [over.ics.user]:

A conversion of an expression of class type to the same class type is given Exact Match rank, and a conversion of an expression of class type to a base class of that type is given Conversion rank, in spite of the fact that a copy/move constructor (i.e., a user-defined conversion function) is called for those cases.

For the list initialization case, you probably need to look at: 13.3.3.1.2 User-defined conversion sequences [over.ics.user]

When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:

— Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.

— If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

Because the overload resolution has to look at viable contructors in each case for f(A) and f(B) it must reject the seqence trying to bind A() to A(A&) but B(const A&) is still viable.

Chao answered 6/4, 2013 at 15:21 Comment(3)
Thanks! I can find no such rule for the {...} case. Does that explain why the {...} case works?Redbreast
@JohannesSchaub-litb: I'm not sure, tbh, you're calling the function with a braced-init-list so the rules are definitely different.Chao
@JohannesSchaub-litb See [over.ics.list]. I think that has to do with [over.ics.ref]/3 (I misread your code earlier): When forming the subset of viable functions, the ctor A(A&) is not considered viable as it binds a temporary to a non-const lvalue reference.Cheyennecheyne

© 2022 - 2024 — McMap. All rights reserved.