6
votes

I've got 2 very simple classes, ClassA and ClassB. I want to be able to cast from ClassB to ClassA. Using Sun C++ 5.11, it compiles fine and runs exactly as I expect it to, according to this: Conversion constructor vs. conversion operator: precedence.

When I try to compile it using gcc version 4.8.2, it gives an error about an ambiguous call to overloaded function. Why would this behave differently when casting appears to be a fairly well-defined behaviour?

The Code:

main.cc

#include <iostream>

class ClassB;

class ClassA
{
    public:
    ClassA( const int& num )
    : _number( num )
    { std::cout << "ClassA int constructor\n"; }

    private:
    int _number;
};

class ClassB
{
    public:
    ClassB( const int& num )
    : _number( num )
    { std::cout << "ClassB int constructor\n"; }

    operator ClassA() const throw()
    {
        std::cout << "ClassB operator ClassA()\n";
        return ClassA( _number );
    }
    operator int() const throw()
    {
        std::cout << "ClassB operator int()\n";
        return _number;
    }

    private:
    int _number;
};

int main( int argc, const char* argv[] )
{
    std::cout << "Creating b:\n";
    ClassB b( 5 );
    std::cout << "Casting b to a ClassA:\n";
    ClassA a = static_cast<ClassA>( b );
}

Using Sun C++ 5.11, it compiles fine and spits out the following output:

Creating b:
ClassB int constructor
Casting b to a ClassA:
ClassB operator ClassA()
ClassA int constructor 

Using gcc, the compiler spits out the following error:

main.cc: In function 'int main(int, const char**)':
main.cc:43:36: error: call of overloaded 'ClassA(ClassB&)' is ambiguous
  ClassA a = static_cast<ClassA>( b );
                                    ^
main.cc:43:36: note: candidates are:
main.cc:8:2: note: ClassA::ClassA(const int&)
  ClassA( const int& num )
  ^
main.cc:5:7: note: ClassA::ClassA(const ClassA&)
 class ClassA

If I comment out ClassB::operator int() or ClassA::ClassA( const int& ), it compiles fine and gives the same output. If I comment out ClassB::operator CLassA(), I get the following output:

Creating b:
ClassB int constructor
Casting b to a ClassA:
ClassB operator int()
ClassA int constructor

Why does gcc consider these two conversion sequences equivalent:

ClassB::operator ClassA()
ClassB::operator int() -> ClassA::ClassA( const int& )

2
This is way way way too much code for demonstrating this problem. - chris
Some additional context: in the actual code this appears in, there is one "ClassA", but there are lots of different "ClassB"s that need to be converted to "ClassA." A solution that's been proposed is to implement a method ClassB::toClassA(), which returns a ClassA object in the same was ClassB::operator ClassA() would. Although that will make this compile and work properly, I'd like to understand what's actually happening that makes the current approach not work. - Kovaz
@chris: How so? In order to demonstrate it, I need the two classes, one with several conversion operators and one with several constructors. I also tried to keep the implementation as similar to the actual classes as possible, in case some implementation detail I'm overlooking is the problem. - Kovaz
You should put it in one file that we can copy-paste into an editor (since the question isn't about multiple files). And instead of worrying about it being close to your code, you should cut it down as much as you can and then verify that it still exhibits the same problem. - chris
Including the code for the debug output actually was helpful. - Ben Voigt

2 Answers

4
votes

If your compiler supports C++11 features you can declare your conversion operators explicit:

class ClassB
{
    public:
    explicit ClassB( const int& num );

    explicit operator ClassA() const throw();
    explicit operator int() const throw();
    explicit operator float() const throw();

    int getNumber() const;

    private:
    int _number;
}

Display

Thus, compiler won't get confused in overload resolution with ambiguous calls that static_cast<> has to choose from due to implicit conversions.

1
votes

This should work:

int main( int argc, const char* argv[] )
{
    std::cout << "Creating b:\n";
    ClassB b( 5 );
    std::cout << "Casting b to a ClassA:\n";
    ClassA a = b.operator ClassA();
}

What's going on here is that static_cast is getting confused between your various conversion operators. It is trying to get from ClassA to ClassB, but since C++ allows one implicit conversion in a function call and there are multiple possible conversion paths (ClassB to float to ClassA, ClassB to int to ClassA, ClassB directly to ClassA), the compiler complains about the ambiguity. (Well, before your edit the float path was there...)

Calling the desired conversion operator directly resolves the ambiguity because there are no longer any potential implicit conversions.

EDIT: 40two's answer below (using explicit on the conversion operators) also nicely removes the ambiguous paths while still allowing direct casting. It's probably the way you want to go.