views:

245

answers:

8

I'm writing a program under MS Visual C++ 6.0 (yes, I know it's ancient, no there's nothing I can do to upgrade). I'm seeing some behavior that I think is really weird. I have a class with two constructors defined like this:

class MyClass
{
public:
    explicit MyClass(bool bAbsolute = true, bool bLocation = false) : m_bAbsolute(bAbsolute), m_bLocation(bLocation) { ; }
    MyClass(const RWCString& strPath, bool bLocation = false);

private:
    bool m_bAbsolute;
    bool m_bLocation;
};

When I instantiate an instance of this class with this syntax: MyClass("blah"), it calls the first constructor. As you can see, I added the explicit keyword to it in the hopes that it wouldn't do that... no dice. It would appear to prefer the conversion from const char * to bool over the conversion to RWCString, which has a copy constructor which takes a const char *. Why does it do this? I would assume that given two possible choices like this, it would say it's ambiguous. What can I do to prevent it from doing this? If at all possible, I'd like to avoid having to explicitly cast the strPath argument to an RWCString, as it's going to be used with literals a lot and that's a lot of extra typing (plus a really easy mistake to make).

A: 

Not sure why it should confuse a reference to a string and a bool? I have seen problems with a bool and an int.
Can you lose the default value for the first constructor - it may be that since this is making it the default constructor for MyClass() then it is also the default if it can't match the args

Martin Beckett
the C++ standard allows any pointer to be converted to a boolean, IIRC, which is why the const char * -> bool is allowed in the first place. I just don't know why it seems to like that one better. Removing the default arg is non-ideal, and likely wouldn't make a difference, but I'll give it a try.
rmeador
A: 

Are you certain that it really is calling the first constructor? Are you calling it with the string hard-coded, or is it hidden behind a #define? Are you sure that the #define is what you think it is? (Try compiling with the /Ef option to get the expanded preprocessor output, and see if the call looks like you'd expect it to look.)

EDIT: Based on this and other comments, my suggestion is to add another constructor for const char*. Is that feasible?

Dan Breslau
I'm calling it with a bare string literal, no #define, no constant defined elsewhere. And I'm about as certain as I can be that it's calling the first one, since that's where it jumps to when I step through in the debugger :)
rmeador
A: 

It's been a while since I've done C++, but to the best of my recollection, there is not a built-in conversion from const char * to bool. If the code is truly calling that constructor, there has to be a conversion operator somewhere that converts a const char * to a bool. Perhaps a helper function that tests for a null string?

You might try rebuilding the entire solution to see if that fixes the problem.

EDIT: Probably should've stayed out of this one. I've been coding in C# too long, where conversions like this don't just "happen."

Matt Davis
Probably due to the while, but imho any pointer can be converted to a bool, false whether the pointer was null, true otherwise.
xtofl
You may be right, but in this case, wouldn't a cast be required to convert the pointer to a bool?
Matt Davis
Rob K
@Rob K: Thanks. I just ran a test in C++ with Visual Studio 2008 to prove to myself that the compiler actually allows this. It has been a while! ;)
Matt Davis
+9  A: 

Explicit will not help here as the constructor is not a part of the implicit conversion, just the recipient.

There's no way to control the preferred order of conversions, but you could add a second constructor that took a const char*. E.g:

class MyClass
{
public:
    MyClass(bool bAbsolute = true, bool bLocation = false);
    MyClass(const RWCString& strPath, bool bLocation = false);
    MyClass(const char* strPath, bool bLocation = false);

private:
    bool m_bAbsolute;
    bool m_bLocation;
};
Andrew Grant
+1  A: 

If you don;t want to keep casting it, then it seems to me that you might have to make another ctor that takes a const char*.

That is what I would probably do in this situation.

(Not sure why you are making a ctor with a type that you aren't passing for most of its use.)

edit:

I see someone else already posted this while I was typing mine

Tim
my hope was the RWCString would handle both the literal string and the RWCString uses. It's actually mostly used with RWCString; the literals only come in with some hard-coded (not my choice) configuration data.
rmeador
A: 

Your choices are to add a constructor that explicitly takes a const char *

MyClass(const char* strPath, bool bLocation = false); // Thanks Andrew Grant!

Or do

MyClass( string("blah") );

The compiler intrinsically knows how to make a const char * into a bool. It would have to go looking to see if, for any of the first-argument types of MyClass constructors, there's a constructor that will take the source type you've given it, or if it can cast it to a type that is accepted by any of the constructors of any of the first-argument types of your MyClass constructors, or... well, you see where this is going and that's only for the first argument. That way lies madness.

Rob K
A: 

The explicit keyword tells the compiler it cannot convert a value of the argument's type into an object of your class implicitly, as in

struct C { explicit C( int i ): m_i(i) {}; int m_i; };
C c = 10;//disallowed
C c( 2.5 ); // allowed

C++ has a set of rules to determine what constructor is to be called in your case - I don't know from the back of my head but you can intuitively see that the default arguments lead towards ambiguity.

You need to think those defaults through.

You can fallback onto some static, named, construction methods. Or you can use a different class (which is not a bad choice from a design viewpoint). In either way, you let the client code decide which constructor to use.

struct C {
  static C fromString( const char* s, const bool b = true );
  static C fromBools( const bool abs = true, const bool b = true );
};

or

struct CBase {
    bool absolute; 
    bool location;
    CBase( bool abs=true, bool loc=true );
};

struct CBaseFromPath {
    // makes 'absolute' true if path is absolute
    CBaseFromPath( const char* s, const bool loc );
};
xtofl
+4  A: 

Andrew Grant provided the solution. I want to tell you why it doesn't work the way you tried. If you have two viable functions for an argument, then the one that matches the argument best is called. The second requires a user-defined conversion, while the first only needs a standard conversion. That is why the compiler prefers the first over the second.

Johannes Schaub - litb