4

I've discovered a discrepancy between the Microsoft Visual C++ compiler, and gcc-4.8.1 (as provided by ideone.com). Consider the following SSCCE:

struct S
{
  int x;
};

class A
{
public:
  int x;
  A(const S& s) : x(s.x) {}
};

class B
{
  int x, y;

public:
  template <typename T> explicit B(const T& t) : x(t.x), y(t.y) {}

  B(const A& a) : x(a.x), y(0) {}
};

int main() {
  S s = {1};

  B b1 = s; // Compiles OK on MSVC++;
            // Fails on gcc - conversion from ‘S’ to non-scalar type ‘B’ requested

  B b2(s);  // Fails on both - Error: y is not a member of S in B::B<S>(const T &)
}

I understand why the line B b2(s); fails - the explicit constructor matches so it's tried; but t.y doesn't exist. Fine.

But I can't work out whether MSVC++ is correct in allowing B b1 = s;, or whether gcc is correct in rejecting it. MSVC++ is constructing a temporary from A::A(const S&), and using that to initialise b1 via B::B(const A&); I'm not sure why gcc errors.

Which compiler's right?

(As an after note, if I remove the explicit both compilers reject B b1 = s; - presumably because the templated constructor is now fair game for the implicit construction of the temporary.)

Edit: From the comments, it appears the MSVC++ also rejects the B b1 = s; line in Visual Studio 2012, so the consensus seems to be it really is an error. In which case - what is the nature of the error? What does that error message mean?

Chowlett
  • 45,935
  • 20
  • 116
  • 150
  • VS2012 also fails with `B b1 = s;`. I believe the failure is the correct action. Which VS didn't fail? – egur Jan 09 '14 at 10:40
  • Interesting. My version is VS2010, so it could be a fixed bug (which will likely break our code when we upgrade!). I'm still interested to know what's going on, though. – Chowlett Jan 09 '14 at 10:42
  • @egur - what's the error message in 2012? – Chowlett Jan 09 '14 at 10:52
  • 2
    It's an error because to construct a B from an S would require two user-defined conversions in a row (S -> A -> B), whereas only one is allowed. VS2010 is probably allowing it. – Simple Jan 09 '14 at 10:56
  • @Simple I posted an answer, feel free to correct me if I'm wrong on any details. –  Jan 09 '14 at 11:00
  • @Chowlett - VS2012: error C2039: 'y' : is not a member of 'S' – egur Jan 09 '14 at 12:03
  • MSVC accepts the two step conversion as a language extension. If you want to disable MS language extensions then compile with the /Za flag. When you do this the example will be rejected by the compiler. – Rastaban Jan 10 '14 at 23:28

1 Answers1

5

Stolen from this answer, the standard says:

12.3 Conversions [class.conv]

4 At most one user-defined conversion (constructor or conversion function) is implicitly applied to a single value.

You're trying to perform two in one step, S which needs to be converted for B's constructor that accepts an A, and then another for A's constructor that accepts an S. The solution is to first cast S into an A:

B b1 = static_cast<A>(s);
Community
  • 1
  • 1
  • This is plainly correct; but you could argue that `S -> A` to make a temporary, then `A -> B` to copy-initialise are each one user-defined conversion applied to different objects. Also, what is `conversion from ‘S’ to non-scalar type ‘B’ requested` trying to tell me? – Chowlett Jan 09 '14 at 11:07
  • @Chowlett A struct/class is a non-scalar type. I think the error message is confusing. –  Jan 09 '14 at 11:12
  • 1
    MSVC accepts the two step conversion as a language extension. If you want to disable MS language extensions then compile with the /Za flag. When you do this the example will be rejected by the compiler. – Rastaban Jan 10 '14 at 23:27