6

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& )

Community
  • 1
  • 1
Kovaz
  • 127
  • 6
  • 7
    This is way way way too much code for demonstrating this problem. – chris Jun 06 '14 at 20:07
  • 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 Jun 06 '14 at 20:10
  • @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 Jun 06 '14 at 20:13
  • You figure it out. Most of your code is irrelevant. – juanchopanza Jun 06 '14 at 20:18
  • 3
    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 Jun 06 '14 at 20:19
  • I agree that it's bad design, but I'm converting a huge amount of code that I didn't write and I'm trying to keep my changes as minimal as possible. This isn't even close to the worst coding practice I've run into. – Kovaz Jun 06 '14 at 20:37
  • 1
    Including the code for the debug output actually was helpful. – Ben Voigt Jun 06 '14 at 20:38
  • oops, I put the debug messages back in my code but forgot to copy-paste over – Kovaz Jun 06 '14 at 20:46

2 Answers2

4

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.

101010
  • 41,839
  • 11
  • 94
  • 168
1

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.

mwigdahl
  • 16,268
  • 7
  • 50
  • 64
  • I personally don't think it's bad practice, per se. It is a more obscure idiom so I would make an effort to explain what I was doing in comments for the benefit of future maintainers, but there's nothing inherently wrong with it -- it does pretty much exactly what it says on the label. – mwigdahl Jun 06 '14 at 20:55
  • Is there a more obvious idiom I could be using? Right now the code I'm working with is littered with casts of all sorts from ClassB to ClassA. I had hoped that I could fix it by just changing ClassB somehow as that would be the most minimal change, but if I'm going through and changing the casts themselves I'd like to use the most correct way possible. – Kovaz Jun 06 '14 at 20:59
  • If you want to limit the scope of change to `ClassB` to make the existing casts work, the only thing to do would be to remove the ambiguity in the conversions. Is it possible for you to eliminate all the other user-defined conversions except for the ClassB-to-ClassA one? – mwigdahl Jun 06 '14 at 21:10
  • Actually, 40two's idea probably gets you where you want to go with minimal change, with the gcc version you have. – mwigdahl Jun 06 '14 at 21:13