ACCU Home page ACCU Conference Page
Search Contact us ACCU at Flickr ACCU at GitHib ACCU at Google+ ACCU at Facebook ACCU at Linked-in ACCU at Twitter Skip Navigation

pinA Return Type That Doesn’t Like Being Ignored

Overload Journal #53 - Feb 2003 + Programming Topics   Author: Jon Jagger

list<type>::insert

A while ago I finished a contract to write a small subset of STL targeted at embedded C++. This proved an interesting challenge. For example, in full STL, you insert a value into a list using, you’ll never guess, list::insert.

template<typename type>
list<type>::iterator
list<type>::insert(iterator position, const type& value) {
...
}

This returns an iterator to the inserted copy of the value or it throws an exception (allocating the internal list node could throw std::bad_alloc for example). However, you can’t use this insert in embedded C++ for two reasons.

  1. Embedded C++ does not support templates. I regard this as a minor problem. In this case templates are being used to get the compiler to write code. You can fake this use of templates quite easily if you put your mind to it.

  2. Embedded C++ does not support exceptions. This is definitely not a minor problem. What to do?

The role of failure

My first choice was to emulate exceptions. However, for various understandable reasons, my client decided not to adopt an exception-like mechanism. I’ll explain what I did shortly, but first I’d like to make a short diversion and explain why emulating exceptions is my strong preference. It’s all to do with failure [1].

The role of failure is fundamental to writing good software. When you use STL list<type>::insert you don’t have to worry about its return value if it fails because if it fails it doesn’t return. This allows you to write sequences of expressions and statements without having to constantly check if anything went wrong. This is really important.

#ifndef LOUD_BOOL_INCLUDED
#define LOUD_BOOL_INCLUDED

class loud_bool {
    public: // construction/destruction

    loud_bool(bool decorated);
    loud_bool(const loud_bool&);
    ~loud_bool();

public:     // conversion
    operator bool() const;
private:    // inappropriate
    loud_bool& operator=(const loud_bool&);
private:    // state
    bool result;
    mutable bool ignored;
};
#endif
Listing 1: loud_bool.hpp

To use an analogy, consider taking a short walk to the local shop for a pint of milk. Do you:

a) check whether you haven’t died after every step, or
b) just walk to the shop and hope you don’t die.

Answer; you just walk to the shop of course. No checks if you’ve died yet. If something serious happens (eg you’re hit by a bus) then you won’t get to the shop but frankly you won’t care much about the milk by then anyway. If you’re still alive you’ll be much more concerned about getting to the nearest hospital.

The point is that constantly checking if you’ve died is silly not because it slows your journey so massively but because it’s exactly the wrong thing to be concerned about in the first place.

The right thing to be concerned about is what happens if you are hit by a bus. The reality is simple. If you are hit by a bus you won’t be in much of a state to do anything so the only sensible and practical approach is to make arrangements before you set off.

My conclusion is this: The software model of use that exceptions bring with them is so valuable that if you’re working in an environment (or language subset) that doesn’t support exceptions you should consider ways in which you can emulate them. The alternative is constantly checking if you’ve died.

Back to the story

But as I said, my client decided not to adopt an exception-like mechanism. So what did I do? I considered making list::insert return an iterator equal to end() if the insertion failed but decided this was not a good idea. When things are different they should be clearly different.

#include "loud_bool.hpp"
#include <ios>
#include <iostream>

loud_bool::loud_bool(bool decorated)
    : result(decorated),
      ignored(true) {
    // all done
}

loud_bool::loud_bool(const loud_bool & other)
    : result(other.result),
      ignored(other.ignored) {
    other.ignored = false;
}

loud_bool::~loud_bool() {
    if (ignored) {
        std::cerr.setf(std::ios_base::boolalpha);
        std::cerr << "WARNING: return "
                  << result
                  << "; is being ignored"
                  << std::endl;
    }
}

loud_bool::operator bool() const {
    ignored = false;
    return result;
}
Listing 2: loud_bool.cpp

One option is to return a bool and a list::iterator inside a pair-like structure. However, as it turned out, my client’s particular list requirement didn’t require the returned iterator, so the version of list<type>::insert I wrote looked like this:

bool list::insert(iterator position, const type& value);

Lacking exceptions, this list::insert returns a bool to signify success or failure. The problem now of course is that the bool return is just too easy to ignore. In contrast, exceptions, by design, are impossible to ignore. In full STL if you’re not interested in the iterator return value, ignoring it is precisely what you do. I thought about this for a while before realizing there was a solution. The problem is that if you ignore a bool nothing happens. So the answer is not to use a bool! Use something that looks like a bool, smells like a bool, feels like a bool, and shouts loudly when ignored. (In practice, this version for embedded C++ has to be modified slightly because embedded C++ does not support the mutable keyword either.) See Listings 1-3.

The loud_bool class generalizes to the template class shown in Listings 4-6 for those of you working in full C++ (I’ve not put it in a namespace to save horizontal space).

#include "loud_bool.hpp"

loud_bool example1() {
    //...
    return true;
}

int main() {
    bool result = example1();   //no warning
    example1();                 //generates warning return 0;
}
Listing 3: Example use of loud_bool
#ifndef WARN_IF_IGNORED_INCLUDED
#define WARN_IF_IGNORED_INCLUDED

template<typename result_type>
class warn_if_ignored
{
public:     // construction/destruction
    warn_if_ignored(const result_type& decorated);
    warn_if_ignored(const warn_if_ignored&);
    ~warn_if_ignored();
public:     // conversion
    operator result_type() const;
private:    // inappropriate
    warn_if_ignored& operator=(const warn_if_ignored&);
private:    // state
    result_type result;
    mutable bool ignored;
};

#include "warn_if_ignored-template.hpp"
#endif
Listing 4: warn_if_ignored.hpp

The final step (left as exercise for the reader) is to parameterize the behaviour if the result is ignored.

Jon Jagger
jon@jaggersoft.com

References [1] To Engineer is Human. The Role of Failure in Successful Design. Henry Petroski. Vintage. 0-679-73416-3

#include <ios>
#include <iostream>

template<typename result_type>
warn_if_ignored<result_type>::warn_if_ignored( const result_type & decorated)
    : result(decorated),
      ignored(true) {
    // all done
}

template<typename result_type>
warn_if_ignored<result_type>::warn_if_ignored( const warn_if_ignored & other)
    : result(other.result),
      ignored(other.ignored) {
    other.ignored = false;
}

template<typename result_type>
warn_if_ignored<result_type>::~warn_if_ignored() {
    if (ignored) {
        std::cerr << "WARNING: return "
                  << result
                  << "; is being ignored"
                  << std::endl;
    }
}

template<typename result_type>
warn_if_ignored<result_type>::operator result_type() const {
    ignored = false;
    return result;
}
Listing 5: warn_if_ignored-template.hpp
#include "warn_if_ignored.hpp"

warn_if_ignored<bool> example2() {
    // ...
    return false;
}

int main() {
    bool result = example2();   //no warning
    example2();                 //generates warning return 0;
}
Listing 6: Example use of warn_if_ignored template

Overload Journal #53 - Feb 2003 + Programming Topics