wrong argument conversion preferred when calling function
Asked Answered
A

7

3

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

Aeromechanic answered 19/3, 2009 at 19:39 Comment(0)
T
9

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;
};
Tranquilize answered 19/3, 2009 at 19:50 Comment(0)
D
6

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.

Duong answered 19/3, 2009 at 20:16 Comment(0)
B
1

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

Bibliotherapy answered 19/3, 2009 at 19:54 Comment(1)
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.Aeromechanic
U
0

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

Unparalleled answered 19/3, 2009 at 19:43 Comment(1)
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.Aeromechanic
L
0

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.

Legendary answered 19/3, 2009 at 20:8 Comment(0)
B
0

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 );
};
Busload answered 19/3, 2009 at 20:14 Comment(0)
M
-1

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?

Minster answered 19/3, 2009 at 19:46 Comment(1)
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 :)Aeromechanic

© 2022 - 2024 — McMap. All rights reserved.