Incorrect overload / specialization due to improper conversion

After an error in my own code, where (in my opinion) the wrong overload was chosen by the compiler, I searched for an explanation, but could not find a simple one. I found Herb Sutter GOTW 49 , which is dedicated to the issue of specialization. I also found some questions about stackoverflow, but none of them could explain the reason for me, nor provide me with good solutions.

I have one Foo class that can be built from a boolean. I found out (the hard way) that std :: string can also be built from bool (false).

I have three (boilerplate) methods with different arguments, as shown below. One method accepts "any" template argument and two specializations that take a Foo structure, and the other takes 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 tested this with Visual Studio 2010 and with MinGW (gcc 4.7.0). GCC gives warning compilation fine, but msvc does not:

 main.cpp:34:20: warning: converting 'false' to pointer type for argument 1 of 'std::basic_string< ... ' [-Wconversion-null] 

Minor update (in code): Even explicit specialization does not work with Foo. \

Small update 2: the compiler does not complain about "ambiguous overload".

Minor update 3: Some people say that two Foo constructors that accept a bool will “invalidate” Foo. I tested similar versions with only one conversion constructor. They also do not work.

Questions:

  • Why is the compiler trying to call a version of a string argument?
  • And why adding the <Foo> addon to the bar () line matters.
  • How can I prevent this. For instance. can I get the compiler to select bar( const Foo& ) when entering bool?
  • Alternatively, is it possible to force compilation when calling bar< Foo >( false ) ?
+4
source share
3 answers

Here are the answers to four questions:

  • Why is the compiler trying to call a version of a string argument? Your Foo class has two constructors that accept bool that are ambiguous and thus the conversion of bool to Foo not considered (if you want to determine if a temporary or lvalue passed, you can overload in C ++ using bool&& and bool const& as types of parameters). std::string can be built from a char const* and false can be increased to a pointer constant of zero. The null pointer char const* is syntactically a valid char const* , but this is an illegal value, and passing it causes undefined behavior.
  • And why does the addon in the bar () line matter? When specifying a template argument, you suppress the output of the template argument and tell the compiler which argument to use. Specifying a template argument explicitly is the only way to overload bar() with std::string , since T cannot be output for this template.
  • How can I prevent this. For instance. can I get the compiler to select the panel (const Foo &) when bool is entered? The easiest way is to force the compiler to output the template argument: the bar() version is never automatically selected by the compiler, because the compiler cannot output the template argument. Alternatively, if you want to explicitly specify a template argument, and only bool is the problem, you can add overload with bool and make the delegated version and delegate version of bool the same internal function.
  • Alternatively, is it possible to force a compilation error when someone calls bar <Foo> (false)? This would be easy with C ++ 2011 by removing overload (see below).

To create a compile-time error when using bool with an explicit template argument, you can add this overload:

 template <typename T> void bar(bool) = delete; 

The delete function is available in C ++ 2011.

The main question is: if Foo and std::string can be converted from bool , why is the conversion of std::string selected if bar<Foo>(bool) is called and the following overloads are available?

 template <typename T> void bar(T const&); template <> void bar<Foo>(Foo const&); template <typename T> void bar(std::string const&); 

First, overload resolution selects the primary template, ignoring any specializations). Since bar(std::string const&) is a more specialized interface than the version outputting the template argument, this version is selected. At this point, the specialization of the first template is ignored. To use the ability to use Foo , you can add

 template <typename T> void bar(Foo const&); 

and calling bar<Foo>(false) will be an ambiguity between the versions of std::string and Foo .

+4
source

First example, bar( false ); calls template<typename T> void bar( const T& value ) , because this is an exact match with T = bool.

When you indicate that T = Foo, none of the overloads is an exact match, so you find yourself in fairly complex rules that use implicit conversions. Most C ++ programmers do not fully understand them, so you are probably better off avoiding implicit conversions.

The simplest fix in this situation is to simply add another overload for bool.

 template<typename T> void bar( bool ) { std::cerr << "bool" << std::endl; } 

then in this overload you can explicitly apply the conversion and invoke the version you need.

+2
source

This line

 bar< Foo >( false ); 

can match either of these two functions:

 void bar<Foo>(const Foo&); void bar<Foo>(const std::string&); 

Now, if both UDTs have implicit conversion constructors from bool , which one should it select? Both of them have the same priority, because both of them have the same type of conversion.

At least ... it is assumed that what is happening, although in fact one should not choose promotion + construction in a sequence only for construction.

+1
source

Source: https://habr.com/ru/post/1440802/


All Articles