After a bug in my own code, where (in my opinion) the wrong overload got selected by the compiler, I have been digging for an explanation but can not find an easy one. I did find Herb Sutter’s GOTW 49 which deals with the issue of specialization. I also found a few questions on stackoverflow, but none could really explain the cause to me, nor provide me with good solutions.
I have a single class Foo, which can be constructed from a boolean. I found out (the hard way) that std::string can also be constructed from a bool (false).
I have three (template) methods with different arguments, as shown below. One method accepts “any” template argument, and two specializations, accepting a struct Foo and another accepts a string.
#include <string>
#include <iostream>
struct Foo
{
Foo() : value( false ){ };
Foo( bool v ) : value ( v ) { }
Foo( const bool& v ) : value( v ) { }
bool value;
};
template< typename T >
void bar( const T& value )
{
std::cerr << "template bar" << std::endl;
}
template< >
void bar< Foo >( const Foo& )
{
std::cerr << "template bar with Foo" << std::endl;
}
template< typename T >
void bar( const std::string& )
{
std::cerr << "template bar with string" << std::endl;
}
int main( int argc, char* argv[] )
{
bar( false ); // Succeeds and calls 1st bar( const T& )
bar< Foo >( false ); // Crashes, because 2nd bar( const std::string& )
// is called with false promoted to null pointer.
return 0;
}
I have tested this with Visual Studio 2010 and with MinGW (gcc 4.7.0). GCC nicely gives a compile-warning, but msvc does not:
main.cpp:34:20: warning: converting 'false' to pointer type for argument 1 of 'std::basic_string< ... ' [-Wconversion-null]
Small update (in code): Even an explicit specialization with Foo does not work.\
Small update 2: The compiler does not complain about an “ambiguous overload”.
Small update 3: Some people answer that the two constructors of Foo accepting a bool, “invalidate” the selection of Foo. I have tested similar versions with only a single conversion constructor. These don’t work either.
Questions:
- Why does the compiler try to call the string-argument version?
- And why does the additon of
<Foo>in the bar() call matter. - How can I prevent this from happening. E.g. could I force the compiler to select
bar( const Foo& )when a bool is input? - Alternatively, could I enforce a compile-error when someone calls
bar< Foo >( false )?
Here are the answers to the four questions:
Foohas two constructors taking aboolwhich are ambiguous and, thus, convertingbooltoFoois not considered (if you want to detect if a temporary or an lvalue is passed in, you can overload in C++ usingbool&&andbool const&as parameter types).std::stringcan be constructed from achar const*andfalsecan be promoted to a null pointer constant. A null pointer constant is syntactically a validchar const*but it is an illegal value and passing it causes undefined behavior.bar()taking astd::stringcan be chosen becauseTcannot be deduced for this template.bar()s never chosen automatically by the compiler because the compiler can’t deduce the template argument. Alternatively, if you want to specify the template argument explicitly and onlyboolis a problem, you can add an overload takingbooland make the deduced version and theboolversion delegate to the same internal function.To create a compile-time error when using
boolwith an explicitly specified template argument, you could add this overload:Deleting function is available in C++ 2011.
The main question seems to be: If
Fooandstd::stringcan both be converted frombool, why is thestd::stringconversion chosen ifbar<Foo>(bool)is called and the following overloads are available?First, overload resolution chooses the primary template, ignoring any specializations). Since
bar(std::string const&)as a more specialized interface than the version deducing the template argument, this version is chosen. The specialization of the first template is ignored in this stage. To make the use ofFooapplicable as well, you could addand calling
bar<Foo>(false)would be an ambiguity between thestd::stringand theFooversion.