views:

32

answers:

2

I have inherited a template to convert a string to a numerical value, and want to apply it to convert to boolean. I am not very experienced with the stringstream and locale classes. I do seem to be getting some odd behaviour, and I am wondering if someone could please explain it to me?

template<typename T> T convertFromString( const string& str ) const {

std::stringstream SStream( str );  
T num = 0; 
SStream >> num;

return num;  
}  

This works fine until I try the boolean conversion

string str1("1");
int val1 = convertFromString<int>(str1); // ok
string str2("true");
bool val2 = convertFromString<bool>(str2); // val2 is _false_

I spent some time tracking down the problem. I have confirmed that the locale's truename() returns "true".

The problem seems to be with the initialisation of the variable num. I can change the template to this and it works:

template<typename T> T convertFromString( const string& str ) const {

std::stringstream SStream( str );  
T num;  // <----------------------- Changed here
SStream >> num;

return num;  
}  
string str2("true");
bool val2 = convertFromString<bool>(str2); // val2 is _true_

Why does it work? I accept that initialising a bool with '0' is wrong, but why would this cause the SStream>>numconversion to fail?

+2  A: 

Initialising a bool with 0 will reliably set it to false, and this has no effect on the stream extraction.

What is causing your problem is that streams by default only recognize the values 0 and 1 when dealing with booleans. To have them recognize the names true and false, you need to tell that explicitly to the stream with the boolalpha manipulator.

The best way to solve your problems is to specialize the template for bool:

template<> bool convertFromString<bool>( const string& str ) const {
  std::stringstream SStream( str );  
  bool val = false;
  SStream >> val;
  if( SStream.fail() )
  {
    SStream.clear(); 
    SStream >> boolalpha >> val; 
  }    
  return val;  
}  

Note that your change did not make the code work. It just appeared to do so for the single testcase you used. With your change, the function failed to read from the stream and returned an uninitialised value. As any non-zero value will be interpreted as true, the function appears to work, but as soon as you try to extract "false", you will see it fail (the function still returns true).

Edit: Adapted the code to handle both numeric and alpha bools.

Bart van Ingen Schenau
That makes sense. Since posting I had extracted the code from the main archive to test by itself and it seemed to work. The inconsistency though - as you say - is the unitialised boolean (mostly `true` in one context, mostly `false` in another).
youngthing
Thank you for providing the solution (boolalpha).
youngthing
To recognise both true/false and 1/0 strings it seems to be useful to try one, then try the other if that fails: if( `(SStream>>num).fail()){ SStream.clear(); SStream>>boolalpha>>num; }
youngthing
Good catch. I have adapted my answer to include that.
Bart van Ingen Schenau
A: 

This is because the stringstream conversion from 'true' fails - your function template should check SStream.fail() before it returns, so you can discover similar failures more easily.

When you init the bool its value is false. When you don't init it (after you remove the = 0), it is random garbage (usually non-zero) and returns true.

template<typename T> T convertFromString( const string& str ) {

std::stringstream SStream( str );  
T num = 0; 
SStream >> num;

if (SStream.fail())
{
    // conversion failure - handle error
    bool error = true;
}

return num;  
} 
Steve Townsend