Hey all,

I was taking a look at my stringstream conversion function and I couldn't decide how I want to handle a bad conversions. I used an exception and then in main I would a use a try, catch block to get any exception.

#include <sstream>
#include <exception>

class ConversionException: public std::exception
{
    virtual const char* what() const throw()
    {
        return "Bad Conversion";
    }
} convertError;

template<typename To, typename From>
const To Convert(const From & convertFrom)
{
    To convertTo;
    std::stringstream ss;
    ss << convertFrom;
    if(ss >> convertTo)
        return convertTo;
    throw convertError;
}

My question is would it be better to write the function using a bool to single success or failure like

template<typename To, typename From>
bool Convert(const To & convertTo, const From & convertFrom)
{
    std::stringstream ss;
    ss << convertFrom;
    if(ss >> convertTo)
        return convertTo;
    return false;
}

Recommended Answers

All 7 Replies

I'm sure there are plenty of educated opinions on how to perform arbitrary type-conversions. Mine is: exceptions and exception-handling are important where you have insufficient control over determining whether an operation will succeed, but using the tried-and-true method of returning a success/failure indication is preferred, simply because it is more efficient (exception handling takes place outside of the normal function-call (and other lexical scoping) stack. Of course, I was raised in the pre-OO dinosaur days, and my early training has stuck with me.

On the opposite side of the argument, exception-handling may be thought of as more elegant. A divide-by-zero error 8 function calls deep doesn't need to be error-handled through each intervening function, and you can code "in the positive" ... assuming that everything will work correctly which is mostly the case, rather than laboriously making every function responsible for verifying the validity of its inputs and checking the success/failure status of every function called from within it: the argument here is that easily 80% of the code in a non-trivial function can end up being error-checking, rather than actually accomplishing the intended task.

For your specific use-case, you can certainly check the success of the ">> convertTo" operation (but remember to return "true" rather than "convertTo"), but what happens if your From-type or To-type don't implement a stringstream & operator<<(...) or stringstream & operator>>(...), respectively? Then you still get an exception, and you haven't handled it!

Mike wrote up an excellent discussion in this thread on "template meta-programming", and specifically how to check at compile-time (rather than run-time) whether a proposed combination of types will work in a given template. It's quite a lot to wrap my head around (I bookmarked it and will re-read it a couple more times in the near future), so don't worry yet if it makes no sense at all; if you can understand the basic premise -- that you can get your compiler to tell you if a coded template use-case will work or not -- you're most of the way there, and the rest is just "details." :)

@raptr_dflo:
First, thanks for your kind words.

Let me give you a quote of me quoting other people:

"In C++, the preferred channel for signaling exceptional situations is by throwing exceptions. This assertion will not be defended here because it has been discussed at great lengths by most pilars of C++: Stroustrup, Sutter & Alexandrescu, Abrahams, and Cline." -- mike_2000_17 (*)


>>using the tried-and-true method of returning a success/failure indication is preferred

"tried-and-true" well.. that's an over-statement. "Tried" because for a long time there was no alternative. "True" because people eventually managed to _painfully_ make it work in the past.

>>simply because it is more efficient

That's a bold statement, and I have never seen empirical evidence to support that claim, which is usually made on theoretical grounds (like the arguments you put forth). In reality, it seems that the difference between efficiency in error-codes and exceptions is very non-trivial and depends highly on the application and how it is used. But one thing is for sure, there is no clear winner on the efficiency side of things.

>>doesn't need to be error-handled through each intervening function

Yes. When you have a dedicated channel provided by C++ for error-handling, it's just stupid not to use it. This begs the question: What's the difference between the overhead of having an exception channel setup and that of having a channel of error-code propagations through return values? I suggest, none or that the exception-channel is better (and automatic too!).

>>you can code "in the positive"

Yes. That's certainly a very nice benefit for sake of clarity and readability of the code.

>>the argument here is that easily 80% of the code in a non-trivial function can end up being error-checking, rather than actually accomplishing the intended task.

On that front, you could say that error-handling code involves three things: 1) propagating errors up-stream (and transforming them); 2) un-doing things that failed (or were done before a failure and should be rolled-back); 3) executing some alternate course of action in response to the error (like aborting the app, or printing an error message, or using an alternate algorithm, etc.). The nice thing with exceptions in C++ is that this mechanism handles the first two tasks automatically (although you can inject your own code easily if you have to), and it lets you concentrate on the really important part of error-handling, which is the third task. And yes, that can significantly reduce and simplify the error-handling code.


However, in my opinion, the main problem with error-code mechanisms is that they don't scale. By definition, the quantity of error-handling code must essentially grow linearly with the amount of sources of error. When using exceptions, you can usually get sub-linear complexity in error-handling, which scales very nicely. For example, if you want to catch a std::bad_alloc exception to detect a run-out-of-memory condition, then you don't have to check it at every point where you allocate memory, you can just check it once, at a much shallower nesting level, and adjust your use of algorithms in consequence.


To be fair, the one problem with exceptions in C++ is that they are kind-of hidden, with no alternative but rigorous documentation to make them known (because exception specification is not a practical option). But, again, error-codes aren't that much better on that front (but still better).


@NathanOliver:

Just to answer your question (if you didn't get my drift yet): Use exceptions!

But the real question is: Why do you need this conversion function template?

raptr_dflo mentioned the idea of preferring compile-time errors to run-time errors, and I obviously agree with that (since he's referring to an earlier post of mine).

The main problem with your conversion operation is exactly that. First, if the conversion is flat-out impossible (like converting a string to a double), then your code will fail at run-time. Second, if your conversion is almost possible, that is, it won't cause a failure on the string-stream read operation, then you will get an erroneous conversion, no exception will be thrown, and the error will be undetectable (i.e. silent) and that's the worst situation you can face.

What you really want when it comes to conversions, I mean, the ideal situation, is that conversions that are impossible or don't make sense should not compile. That's the way you use C++'s strong type-safety to your advantage. It just so happens that C++ has two mechanism to implement this perfectly well: conversion-constructors and conversion operators. That is, you can do this:

class MyInt {
  private:
    int val;
  public:
    //Conversion-constructor:
    MyInt(int aVal) : val(aVal) { };

    //Conversion-operator:
    operator int() const { return val; };
};

Defining either special functions is the way to tell the compiler what conversions are possible and how to perform them. You couldn't dream of an easier, safer and more scalable mechanism for doing this.

I simply must say that I see absolutely no purpose for a generic conversion operator that is implemented via a string-stream write/read. I mean, it's not safe, it's not robust, it's going to cause you tremendous pain in the future (with silent errors), it relies on some sort of magical compatibility between how different types get written to a stream, and you will hardly be able to achieve any conversions that aren't already available in the class definitions (or built-in conversions).

I bow to Mike's expertise here. My notions of efficiency are based entirely on hearsay, and nearly as old as the first C++ compilers; so if he says exceptions are the way to go, then it may not be "The Truth"(tm), but it's likely a more informed and up-to-date opinion than my own. This old dog (referring to myself) can still learn a new trick or two. Thanks for the insight!

#include <sstream>
#include <exception>

class ConversionException: public std::exception
{
    virtual const char* what() const throw()
    {
        return "Bad Conversion";
    }
} convertError;

template<typename To, typename From>
const To Convert(const From & convertFrom)
{
    To convertTo;
    std::stringstream ss;
    ss << convertFrom;
    if(ss >> convertTo)
        return convertTo;
    throw convertError;
}

int main()
{
    double d = 1.0e+8 ;
    std::cout << int(d) << ' ' << Convert<int>(d) << '\n' ; 
    // prints 100000000 1
}

If there is a direct convertible, you should use that instead of performing the conversion via a stringstream. At best it could be inefficient; in many cases it can yield an incorrect result.

Some thing like this would be required:

#include <iostream>
#include <sstream>
#include <type_traits>
#include <stdexcept>

struct bad_conversion : public std::logic_error
{
    bad_conversion() : std::logic_error( "bad conversion" ) {}
};

template< typename TO, typename FROM > inline TO convert( const FROM& from,
    typename std::enable_if<std::is_convertible<FROM,TO>::value,void>::type* = nullptr )
{ return TO(from) ; }

template< typename TO, typename FROM > inline TO convert( const FROM& from,
    typename std::enable_if< !std::is_convertible<FROM,TO>::value, void >::type* = nullptr )
{
    TO to ;
    std::stringstream stm ;
    if( stm << from && stm >> to ) return to ;
    else throw bad_conversion() ;
}

int main()
{
    double d = 1.0e+8 ;
    std::cout << int(d) << ' ' << convert<int>(d) << '\n' ;
    // prints 100000000 100000000
}

This is what TR 18015 : Technical Report on C++ Performance has to say about the costs of exception handling:

For some programs, difficulty in predicting the time needed to pass control from a throw-expression to an appropriate catch clause is a problem. This uncertainty comes from the need to destroy automatic objects and – in the “table” model – from the need to consult the table. In some systems, especially those with real-time requirements, it is important to be able to predict accurately how long operations will take. For this reason current exception handling implementations may be unsuitable for some applications. However, if the call tree can be statically determined, and the table method of EH implementation is used, it is possible to statically analyze the sequence of events necessary to transfer control from a given throw-expression to the corresponding catch clause. Each of the events could then be statically analyzed to determine their contribution to the cost, and the whole sequence of events aggregated into a single cost domain (worst-case and best-case, unbounded, indeterminate). Such analysis does not differ in principle from current time estimating methods used for non-exception code.

In short, rather than making blanket assumptions about the performance cost of throw/catch constructs, one needs to devote some time to analyzing the costs. The results would vary widely depending on both the program being analyzed and the implementation's exception-mechanism design trade-offs.

The entire report is freely available: http://www.open-std.org/jtc1/sc22/wg21/docs/TR18015.pdf
Worth re-reading if C++ performance is an area of concern.

commented: Confirms what I thought! Very interesting report! +14

Well thank you guys very much for your input. By the looks of it I need some more practice with the STL. @ mike_2000_17 if I were to write a string class and wanted the ability to convert it to an int I would I just have to do this?

class MyString
{
    //...
    operator int();
    //...
};

MyString::operator int()
{
    // code for conversion goes here;
}

>>if I were to write a string class and wanted the ability to convert it to an int I would I just have to do this?

Yes.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.