2

Some time ago I was confused by the following behavior of some code when I wanted to write a is_callable<F, Args...> trait. Overload resolution won't call functions accepting arguments by non-const ref, right? Why doesn't it reject in the following because the constructor wants a Test&? I expected it to take f(int)!

struct Test {
  Test() { }

  // I want Test not be copyable from rvalues!
  Test(Test&) { }

  // But it's convertible to int
  operator int() { return 0; }
};

void f(int) { }
void f(Test) { }

struct WorksFine { };
struct Slurper { Slurper(WorksFine&) { } };
struct Eater { Eater(WorksFine) { } };

void g(Slurper) { }
void g(Eater) { } // chooses this, as expected

int main() {
  // Error, why?
  f(Test());

  // But this works, why?
  g(WorksFine());
}

Error message is

m.cpp: In function 'int main()':
m.cpp:33:11: error: no matching function for call to 'Test::Test(Test)'
m.cpp:5:3: note: candidates are: Test::Test(Test&)
m.cpp:2:3: note:                 Test::Test()
m.cpp:33:11: error:   initializing argument 1 of 'void f(Test)'

Can you please explain why one works but the other doesn't?

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • It compiles just fine on my MSVC++ 10 Complier. – cpx Jan 16 '11 at 09:19
  • Okay... I understand.. "Fehler" means "Error".."Anmerkung" means "Note"..haha..learning new human language...which language is this by the way? :| – Nawaz Jan 16 '11 at 09:20
  • @Nawaz oops, I forgot to translate! – Johannes Schaub - litb Jan 16 '11 at 09:24
  • @Johannes : hehe..no problems...i just compiled it with gcc, and I got the translation :P.. and there is google also... – Nawaz Jan 16 '11 at 09:25
  • Just a shot in the blue, does it make a difference if you mark `operator int()` as `const`? – fredoverflow Jan 16 '11 at 09:27
  • @Dave18 only solutions containing reasons beyond "my compiler accepts it" are accepted as answer to the puzzle. – Johannes Schaub - litb Jan 16 '11 at 09:33
  • @JohannesSchaub: What if the correct reason is: "Your compiler is wrong for the reasons stated in the question."? – CB Bailey Jan 16 '11 at 09:44
  • I feel that this topic is somehow related to this : http://stackoverflow.com/questions/4704404/implicit-conversion-const-reference-vs-non-const-reference-vs-non-reference – Nawaz Jan 16 '11 at 09:54
  • @Charles it wouldn't be a sufficient reason for me to accept it, lol. – Johannes Schaub - litb Jan 16 '11 at 09:58
  • @JohannesSchaub: I don't really undestand what you are asking for, then. – CB Bailey Jan 16 '11 at 10:19
  • 1
    Note that Comeau fails with the same error. – avakar Jan 16 '11 at 10:23
  • @Charles i'm for some confirmation for or against the suspicion I had. It can't be given simply by repeating my suspicion. Here we would have my suspicion, your suspicion (?) and MSVC++ against gcc/clang and comeau. I can't see a clear winner. – Johannes Schaub - litb Jan 16 '11 at 10:25
  • @JohannesSchaub: Forget what I said, I totally misread the question! – CB Bailey Jan 16 '11 at 10:27
  • The title of this question is misleading. It's not `Test(Test&)` that is chosen by any overload resolution, it's one of the two `f` which is chosen by overload resolution. The question is which one and why. – CB Bailey Jan 16 '11 at 10:41
  • @Charles I wanted to make it sound like a "real" question, not giving the answer right away. I think you are right, it rather introduces more confusion than question-ness, so since we have a good answer anyway now, I will change the title! – Johannes Schaub - litb Jan 16 '11 at 10:44

2 Answers2

3

Overload resolution picks the function that is the closest match to the supplied argument. You supplied a Test. No conversion necessary -- identity conversion used. Thus function resolution chooses f(Test). Test can't be copied from rvalue, which you supplied, but overload resolution has succeeded already...conversion to int is never checked.

g(Eater) is chosen because the types don't exactly match, the identity conversion is NOT used, and the compiler has to find a conversion routine that works. g(Slurper) doesn't because you can't make one out of the supplied argument.

"Why doesn't this one fail: struct A { operator int(); }; void f(A&); void f(int); void g() { f(A()); }"

Because f(A&) is not a viable overload for the supplied argument. In this case the parameter is a reference and the fact that temps don't bind to non-const is allowed to effect the resolution. In this case it does and the that version of the function becomes a non-candidate, leaving only the one and it works.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
  • Why doesn't this one fail: `struct A { operator int(); }; void f(A&); void f(int); void g() { f(A()); }` and why does it work for my other test? Why is it better to convert `WorksFine` to `Eater` than to `Slurper`? – Johannes Schaub - litb Jan 16 '11 at 10:03
  • @Noah: It's confusing to me that if `Test` can't be copied from rvalue, then how could one say that overload resolution has succeeded? :-/ – Nawaz Jan 16 '11 at 10:04
  • If a parameter has reference type, though, the conversion sequence includes binding the reference. The fact that a reference to non-`const` cannot be bound to an rvalue _should_ affect whether the candidate function is actually viable. – CB Bailey Jan 16 '11 at 10:09
  • @Nawaz - it is a little unintuitive I guess but by my interpretation, since one of the available versions of f has a parameter type exactly matching the type of the expression fed to it, no test is ever done to see if you can actually call that function, constructing a copy of the supplied argument, etc... I don't even think the reference rules apply (which I believe end up saying the same thing because of the "binds directly" stuff) because the text is talking about the type of the parameter, not the argument. – Edward Strange Jan 16 '11 at 10:18
  • @Charles there is actually a paragraph explicitly stating this matter such that clang/gcc/comeau are right. But it's kinda hidden! – Johannes Schaub - litb Jan 16 '11 at 10:19
  • @Charles - it doesn't in this case. f takes a non-reference as parameter. Remember that 'parameter' is explicitly defined as standard speak for the type the function expects (that which is in the definition). – Edward Strange Jan 16 '11 at 10:25
  • @NoahRoberts: Oops, I completely misread the questions. Thanks! – CB Bailey Jan 16 '11 at 10:27
  • @Noah I will do fairplay and accept your answer. Your sayings are correct because it's what 13.3.3.1.2p4 says. – Johannes Schaub - litb Jan 16 '11 at 10:48
  • @JohannesSchaub: What about 13.3.3.1p6 and 13.3.3.1p8? I don't think you should even get into a section about "User-defined conversion sequences". – CB Bailey Jan 16 '11 at 10:58
  • @Charles - yeah, p6/p8 does it in by my interpretation too. There's also a single, solitary (from what I could tell) comment about the "identity conversion" in parentheses in 13.3.3.1.1p2 "(that is, no conversion)". It's the only place I could find it remotely defined to be sure it wasn't defined as calling the copy constructor. – Edward Strange Jan 16 '11 at 11:08
  • @Charles oh indeed. 13.3.3.1p6 looks like a better paragraph to cite than my 13.3.3.1.2p4. Not only better, but I think it's *the* paragraph to cite here, since 13.3.3.1.2p4 doesn't say what exact conversion is used. I stand corrected :) – Johannes Schaub - litb Jan 16 '11 at 11:13
0

Basically, for overload resolution, it is assumed that an object of type A can be converted to an object of type A regardless of any cv-qualification on either of the two.

From draft n1905:

13.3.3.1: Overloading.Overload Resolution.Best Viable Function.Implicit Conversion Sequences

6 When the parameter type is not a reference, the implicit conversion sequence models a copy-initialization of the parameter from the argument expression. The implicit conversion sequence is the one required to convert the argument expression to an rvalue of the type of the parameter. [ Note: when the parameter has a class type, this is a conceptual conversion defined for the purposes of clause 13; the actual initialization is defined in terms of constructors and is not a conversion. — end note ] Any difference in top-level cv-qualification is subsumed by the initialization itself and does not constitute a conversion. [ Example: a parameter of type A can be initialized from an argument of type const A. The implicit conversion sequence for that case is the identity sequence; it contains no “conversion” from const A to A. — end example ] When the parameter has a class type and the argument expression has the same type, the implicit conversion sequence is an identity conversion. [...]

Chris Hopman
  • 2,082
  • 12
  • 11