I have a handy class in C++ that can be initialized either with an double
or with a char*
. Everything works as expected except one case when the argument is zero.
struct Var {
Var (double src) {}
Var (char *src) {}
};
int main(int argc, char* argv[]) {
Var A = 123;
Var B = "abc";
Var C = 0; <- Error: conversion from 'int' to 'Var' is ambiguous
}
Previously I used int
instead of double
, and it was fine for some reason.
How can this be fixed?
PS: I know I can use (double)0
or just 0.0
, but is there a way to allow just 0
to be accepted as double?
You could eliminate the ambiguity by adding a third constructor which takes an int
and delegates to the double
version (requires C++11):
struct Var {
Var (double src) {}
Var (const char *src) {}
// ^^^^^ should be const
Var (int src) : Var {static_cast<double>(src)} {}
};
However, this could be solved easier by changing your char*
constructor to take a std::string
instead and using direct initialization.
In C++, the literal 0
has type int. You cannot change anything about it being an integer, without adding at least a decimal point. (no actual need to write digits after the point though)
To answer you implicit question regarding why the call was unambiguous with 123
but ambiguous with 0
:
When you initialized A
, it was fine because there is no implicit conversion from an int to a pointer (char*
, in this case).
When you initialized C
using an int the second time, you used 0
, the literal 0
can be implicitly converted to a pointer (some legacy behaviour, before C++11 offered nullptr
literal).
So, in a nutshell:
int * a = 0; // legal;
int * b = 2; // ILLEGAL;
When initializing C
one conversion is needed to give the argument to any of the two available constructors (and both conversions have the same ranking), the call is ambiguous.
To make it unambiguous, you should either cast your int literal to a double ((double)0
), use a double literal (0.
), or add a constructor taking an int.
With this last solution, the constructor taking an int
is an exact match, so it is a better match that another overload requiring a conversion (which is why it would be selected).
Edit. Please note that TartanLlama answer should be accepted here as it is a best C++ practice: the constructor taking an C-style string should take a std::string
instead. Since there is an implicit conversion from C-style string to std::string
, the call will work with little change:
Var B("abc");
But you will isolate your class interface from pointers details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With