7

Consider the following C++ code:

struct B { };
struct A
{
        A(int);
        A(A&); // missing const is intentional
        A(B);
        operator B();
};

A f()
{
        // return A(1); // compiles fine
        return 1; // doesn't compile
}

This compiles fine on MSVC++ 2010 (in fact, on MSVC it even works if I remove B altogether). It doesn't on GCC 4.6.0:

conv.cpp: In function ‘A f()’:
conv.cpp:13:9: error: no matching function for call to ‘A::A(A)’
conv.cpp:13:9: note: candidates are:
conv.cpp:6:2: note: A::A(B)
conv.cpp:6:2: note:   no known conversion for argument 1 from ‘A’ to ‘B’
conv.cpp:5:2: note: A::A(A&)
conv.cpp:5:2: note:   no known conversion for argument 1 from ‘A’ to ‘A&’
conv.cpp:4:2: note: A::A(int)
conv.cpp:4:2: note:   no known conversion for argument 1 from ‘A’ to ‘int’

What's confusing me is the message no known conversion for argument 1 from ‘A’ to ‘B’. How can this be true considering that A::operator B() is very well defined?

Etienne Dechamps
  • 24,037
  • 4
  • 32
  • 31
  • I'm not sure how `B` is at all relevant here. You're converting `int` to `A`, don't you? So you omitted the `const` intentionally from the copy constructor, so it doesn't work. What was that you expected to happen? – littleadv Jun 19 '11 at 10:36
  • maybe it cannot convert 1 to `B`? the error message says `for argument [number] 1`. – Vlad Jun 19 '11 at 10:36
  • @littleadv: If `B` is removed, even `return A(1)` doesn't work anymore. The presence of `A::operator B()` and `A::A(B)` allows `A` to be returned from a function even in the absence of a const copy constructor. This trick is used in the C++ standard to implement `std::auto_ptr`: see ISO/IEC 14882:2003, §20.4.5.2 (here `auto_ptr` = `A`, `auto_ptr_ref` = `B`). – Etienne Dechamps Jun 19 '11 at 10:47
  • @Vlad: I'm not sure what you're trying to say. GCC successfully converted `1` to `A`, because the first error message is about `A::A(A)`, not `A::A(int)`. However it fails to convert `A` to `B`, despite the fact that `A::operator B()` and `A::A(B)` are well-defined. I'm trying to understand why. – Etienne Dechamps Jun 19 '11 at 10:51
  • well, @DeadMG's answer looks good for me. – Vlad Jun 19 '11 at 10:54

4 Answers4

5

Because you cannot do more than one implicit conversion. You would have to go A::A(A::A(int)::operator B()) to make that work, and that's way too many steps for the compiler to figure out on it's own.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 1
    I've done some more research, and it appears the C++ standard agrees with you: ISO/IEC 14882:2003, §12.3.4 "At most one user-defined conversion (constructor or conversion function) is implicitly applied to a single value.". – Etienne Dechamps Jun 19 '11 at 11:14
  • 3
    It is important to note that what you are not allowed is to do more than one **user defined** conversion, not implicit conversion. There are other standard conversions that are implicit and that can be chained. `A foo() { return 5.0; }` would start converting from `double` to `int` (implicit floating point conversion) followed by a user defined conversion `A(int)`. You can work out other examples that might include up to 3 non-user defined conversions followed by 1 user defined and at most 3 other non-user defined conversions after the user defined one. – David Rodríguez - dribeas Jun 19 '11 at 11:41
5

I don't think that "too many steps to figure on its own" as DeadMG pointed out is the reason. I've had constructs with 3-4 conversions, and the compiler always figured them out just fine.

I believe the problem is rather that the compiler is not allowed to convert a const reference to a non-constreference on its own behalf (it is only allowed to do that when you explicitly tell it with a cast).
And since the reference to the temporary object that is passed to the copy constructor is const, but the copy constructor is not, it doesn't find a suitable function.

EDIT: I didn't find any "real" code (see comments below) but constructed a multi-zigzag-convert example that actually compiles without errors under gcc 4.5. Note that this compiles just fine with -Wall -Wextra too, which frankly surprises me.

struct B
{
    signed int v;
    B(unsigned short in) : v(in){}
};

struct C
{
    char v;
    C(int in) : v(in){}
};

struct A
{
    int v;
    A(B const& in) : v(in.v){}
    operator C() { return C(*this); }
};

enum X{ x = 1 };

int main()
{
    C c = A(x);
    return 0;
}
Damon
  • 67,688
  • 20
  • 135
  • 185
  • That's what preventing the compiler from using `A::A(A&)`. But that doesn't prevent it from using `A::A(B)` in conjunction with `A::operator B()`, because `const` is irrelevant when passing by value. – Etienne Dechamps Jun 19 '11 at 11:06
  • "I've had constructs with 3-4 conversions, and the compiler always figured them out just fine." What compiler are you using? My code sample compiles fine with MSVC, which seems to indicate GCC and MSVC behave differently in this regard. – Etienne Dechamps Jun 19 '11 at 11:09
  • If you're speaking about user-defined conversions (constructors and conversion functions), then this contradicts the C++ standard. See ISO/IEC 14882:2003, §12.3.4: "At most one user-defined conversion (constructor or conversion function) is implicitly applied to a single value.". I suspect MSVC++ to be too permissive, as it sometimes is. – Etienne Dechamps Jun 19 '11 at 11:13
  • @e-t172: I'm using gcc-minw 4.5 (the "TDM" build) and have been using the 4.4 build before that. Let me see if I can find some working code that does several conversions (though finding that is a real challenge... not sure what to search for). – Damon Jun 19 '11 at 11:28
  • I also thought so, but the error message is confusing `error: no matching function for call to ‘A::A(A)’` – davka Jun 19 '11 at 11:34
  • @Damon: your example is invalid. `A(x)` only triggers one *user-defined* conversion (`B(unsigned short)`; `A(B const&)` is called explicitly). There are some implicit integer conversions, but they're irrelevant because they're not *user-defined* conversions. When converting the (explicit) `A` to `C`, only one user-defined conversion is used: `A::operator C()`. So your code sample is not comparable to mine. I bet that it won't compile if you write `C c = x`, because then `x` would undergo *three* user-defined conversions, which is forbidden by the standard. See David's answer for details. – Etienne Dechamps Jun 19 '11 at 12:06
4

The error is quite clear on the list of candidates that were rejected. The problem is that implicit conversion sequences involving a user defined conversion in the C++ language are limited to a single user defined conversion:

§13.3.3.1.2 [over.ics.user]/1 A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user-defined conversion (12.3) followed by a second standard conversion sequence.

The standard conversion sequences are defined in §4[conv]:

[...] A standard conversion sequence is a sequence of standard conversions in the following order

  • Zero or one conversion from the following set: lvalue-to-rvalue conversion, array-to-pointer conversion, and function-to-pointer conversion.

  • Zero or one conversion from the following set: integral promotions, floating point promotion, integral conversions, floating point conversions, floating-integral conversions, pointer conversions, pointer to member conversions, and boolean conversions.

  • Zero or one qualification conversion.

The problem is that your code cannot get from point a) int rvalue to point b) B by applying a single user defined conversion.

In particular, all conversion sequences that are available start with a user defined conversion (implicit constructor A(int)) that yield an A rvalue. From there, the rvalue cannot be bound to a non-const reference to call A::A( A& ), so that path is discarded. All the other paths require a second user defined conversion that is not allowed, and in fact the only other path that would get us to point b) requires two other user defined conversions for a total of 3.

Community
  • 1
  • 1
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
0

The error lists all the potential candidates to be used, and why they cannot be used. It lists the conversion from B because its one of the constructors, but it doesn't know how to use it in this case, so it doesn't.

littleadv
  • 20,100
  • 2
  • 36
  • 50