Problems with constructor resolution order
Consider the following constructors for T:
struct T {
T(const bool) { std::cout << "T(const bool)" << endl; }
T(const std::string&) { std::cout << "T(const std::string&)" << endl; }
};
T t("");
- Why does
T(const bool)
take precedence overT(const std::string&)
when constructing t? Since the above precedence might cause confusion to users expecting
T(const std::string&)
to be called, what can I do to haveT(const std::string&)
implicitly called when passing string literals to the constructor of T. For now the only work around I have found is to add another constructor, which takes the highest precedence:开发者_StackOverflow中文版T(const char* s) { std::cout << "T(const char*)" << endl; *this = std::string(s); }
Apart from the above solution, declaring
explicit T(const bool)
to avoid confusion does not solve the above problem: in this case, althoughT t = ""
is now prohibited, why is the formT t("")
is still allowed and does callT(const bool)
?
Why does T(const bool)
take precedence over T(const std::string&)
when constructing t
?
""
is of type char[1]
; this is implicitly convertible to char const*
via the array-to-pointer conversion. A pointer is implicitly convertible to bool
, with all non-null pointers becoming true
and all null pointers becoming false
. These are both "built-in" standard conversions.
The char const* -> std::string
conversion is a user-declared conversion: it utilizes a converting constructor of std::string
that takes a char const*
.
The standard ("built-in") conversions are preferred over user-declared conversions during overload resolution, so the constructor taking bool
is a better match here than the one taking std::string
.
For now the only work around I have found is to add another constructor
That sounds like a reasonable solution; certainly the most straightforward solution for the simple scenario you describe. Your use of assignment to *this
is a bit clumsy, though; it would be better either to have both constructors delegate to some initialization function.
Alternatively, you could use a template with enable_if
for any constructors for which you would like to disallow conversions:
template <typename U>
T(U, std::enable_if<std::is_same<U, bool>::value>::type* = 0) { }
This constructor will only be callable with a bool
argument and nothing else. You can find enable_if
and is_same
in Boost, C++ TR1, or C++0x. You could also use !is_pointer
, is_integral
, or some other combination of type traits to allow for some other argument types but not char const*
.
Or, as another alternative, you might eschew bool
altogether and use your own enumeration with enumerators corresponding to true
and false
for the constructor. Whether this makes sense depends on your use case.
Declaring explicit T(const bool)
to avoid does not solve the above problem...why is the form T t("")
is still allowed and does call T(const bool)
?
explicit
only disallows implicit conversions to T
. T t("");
has no conversions to T
at all; it directly initializes the object t
by constructing it with the argument ""
passed to whichever constructor matches best.
""
can convert to both std::string
and bool
.
The question is, which way will it convert?
- Conversion to
std::string
is a user-defined conversion. - Conversion to
bool
is a standard conversion.
So the answer is, standard conversion has higher precedence over user-defined conversion. So ""
will convert to bool
.
Example,
struct A
{
A(int i) {} //i.e an int can implicity convert to A
};
void f(const A &) { cout << "User-defined conversion won" << endl; }
void f(const bool &) { cout << "Standard conversion won" << endl; }
int main() {
f (10);
return 0;
}
Output:
Standard conversion won
Online demo : http://www.ideone.com/5Bt0K
In the above demo, 10
can convert to A
and bool
both. Since converting to bool
is a standard conversion, it converts into bool
, instead of A
.
Because a user-defined conversion is not considered if a built-in one is available.
Use the third constructor, one that takes const char*
. There's no better way.
精彩评论