The Trial of the Reckless Coder

The Trial of the Reckless Coder

By Phil Bass

Overload, 13(67):, June 2005


Overload 67 : June 2005

The Trial of the Reckless Coder

Phil Bass


An Arrest Is Made

"Joe Coder, I'm arresting you on suspicion of coding without due care and attention, and with reckless disregard for the welfare of other code users." I had said this many times before, but this time I was uneasy. Something in Joe's eyes suggested there might be more to this case than I'd imagined.

I hesitated, suddenly unsure of the words of the standard caution. "You do not need to say anything, but anything you do say will be written down and may be used in evidence against you." It was the old caution, the one you heard in films and T.V. programmes. I blundered on, hoping no-one would notice. "Take him away, sergeant", I said a little too loudly. At least I'd resisted the temptation to say "Book 'im, Danno!"

Interrogation

My head was still a little fuzzy from the previous night. Not enough water in my nightcap, probably; or too much whisky. As we prepared to interrogate Joe I had forgotten my qualms about the Bound_Callback<Pmf> class template [ Bass ]. "Did you write this?" I asked, showing Joe the following code:

class Observer
{
public:
    Observer(Event<int>& event)
      : callback(bind_1st(memfun(&Observer::handler), this))
      , connection(event, &callback)
    {}

private:
    void handler(int)
    {
        clog << "Observer's event handler." << endl;
    }

    typedef
        Callback::Adapter<void (Observer::*)(int)>::type
        Callback_Type;

    Callback_Type             callback;
    Callback::Connection<int> connection;
};

Exhibit 1 - The evidence for reckless coding.

"What if I did?" said Joe. "Look, son", I said gently, "the copyright notice has your name on it and the version control log says you checked it in, so there's no use denying it." Joe shrugged. He wasn't going to make it easy for me, but the evidence was solid. "That's a lot of boilerplate code", I continued. "Is it?" Joe responded. His voice sounded innocent, but his eyes were defiant. I wasn't going to waste time in a verbal sparring match, so I showed him a shorter, simpler alternative:

class Observer
{
public:
    Observer(Event<int>& event)
      : callback(event, &Observer::handler, this)
    {}

private:
    void handler(int)
    {
        clog << "Observer's event handler." << endl;
    }

    Bound_Callback<void (Observer::*)(int)> callback;
};

Exhibit 2 - Showing consideration for other code users.

Joe humphed. He was obviously unimpressed. It was a puzzling reaction, but I pressed on. "That Bound_Callback template makes things much easier for the author of the Observer class, doesn't it?" Joe said nothing, but the expression on his face challenged me to prove my point. "Doesn't it?" I yelled, banging the desk with my fist. "Perhaps", said Joe, unflustered.

"Perhaps?" I exclaimed. "There's no 'perhaps' about it, my lad..." Joe interrupted me. "Look, granddad, you haven't given me a specification for the Bound_Callback template, so how can I tell whether it's useful?"

I had to admit he had a point and wrote this on the whiteboard:

Intent

A bound-callback is a callback that is bound to an event for the whole of its life. The Bound_Callback<Pmf> class template implements the bound-callback concept for the common case in which the callback invokes a member function.

Synopsis

template<typename Pmf>
struct Bound_Callback
  : Callback::Function<typename argument<Pmf>::type>
{
    typedef typename argument<Pmf>::type Arg;
    typedef typename   result<Pmf>::type Result;
    typedef typename   class_<Pmf>::type Class;

    Bound_Callback(Event<Arg>&, Pmf, Class*);
   ~Bound_Callback();

    Result operator()(Arg);
};

Note: Here, argument<Pmf> , result<Pmf> and class_<Pmf> are simple class templates with a nested typedef . They are known as meta-functions because they take a type parameter (the pointer-to-member-function type) and 'calculate' another type (the argument type, result type and class of the member function). Further information on meta-functions in general and these meta-functions in particular can be found in [1].

Types

Pmf is the type of a pointer to the member function to be invoked.

Arg is the type of the parameter of the member function to be invoked.

Result is the type of the result of the member function to be invoked.

Class is the class of which the function to be invoked is a member.

Constructor

Bound_Callback(Event<Arg>& event, Pmf pmf, Class* ptr);

Stores a reference to event ; copies and stores the pointer-to-member-function pmf and the pointer ptr ; connects *this to event.

Destructor

Disconnects *this from event.

Function Call Operator

Result operator()(Arg arg);

Invokes the member function pointed to by pmf on the object pointed to by ptr passing the value arg and returning any result.

I was quite pleased with myself. Joe, however, remained unimpressed. "Doesn't seem very flexible", he remarked. "What do you mean?" I said tetchily, "There's nothing inflexible about that!"

It was a mistake. Joe stood up, his eyes sparkled and he attacked my whiteboard specification with vigour. "Well, for a start", he said, "the callback function's argument has to be exactly the same type as that published by the event."

He scrawled the following example on the whiteboard:

// Try to connect handler(int) to Event<short>
class Observer
{
public:
    Observer(Event<short>&)
      : callback(event, &Observer::handler, this)
    {}  // error!

private:
    void handler(int);

    Bound_Callback<void (Observer::*)(int)> callback;
};

Exhibit 3 - Implicit conversions not supported.

"The event publishes short values, the handler function accepts int values and there's an implicit conversion from short to int . You'd expect it to work, but it doesn't." There was a hint of triumph in Joe's voice. He must have seen the puzzled look on my face because he carried on, patronisingly. "Look, granddad, the Bound_Callback<Pmf> constructor takes an Event<int>& as its first argument and we're giving it an Event<short>& . There's no implicit conversion from Event<short>& to Event<int>& , so you get a compilation error."

He was right. I'd been drawn into an intellectual boxing match and I was losing on points. Instinctively, I began to defend the Bound_Callback<Pmf> code. "Yeah, but that's outside the scope of the Bound_Callback<Pmf> specification", I countered. Joe smiled smugly. "My point, precisely", he sneered.

The Case for the Defence

I remembered Joe's smugness when the case came to trial and his defence counsel addressed the jury. "You have heard the prosecution allege that the defendant has been coding with reckless disregard for the welfare of other code users. By their own admission, their case rests almost entirely on the evidence presented in Exhibit 1 and the alternative interface in Exhibit 2 that they have described as more "user friendly". The defence, however, will show that Exhibit 2 is only useful in a rather narrow range of programs - much narrower, in fact, than Exhibit 1. It is our contention that a tool of limited applicability shows greater disregard for code users than one that, in the spirit of C++, allows the user full freedom of expression."

The defence barrister was George Sharpe, an old adversary, and he was revelling in the usual obscure style of language used in courtrooms across the world. My attention began to wander. I couldn't help feeling sorry for the jury. Did they understand what the lawyer was saying? Did they care? Perhaps they were thinking about what they would have for lunch or whether the neighbour had remembered to take their dog for a walk. Suddenly, I realised I had been called for cross-examination.

"Detective Inspector Blunt, how would you define an Event?" Sharpe always starts with an easy question. I gave the stock answer, "An Event is a sequence of pointers to functions." The barrister repeated my answer as a question. "A sequence of pointers to functions?" He says he does this for the jury's benefit - to be sure they understand. But I think it's just to annoy me. "That is the standard definition", I said. "I see. And this covers all kinds of sequence, all kinds of pointer and all kinds of function, does it?" I answered in the affirmative and Sharpe's questions flowed smoothly on. "So, for example, a sequence might be a list or a set or an array?" Before I could answer Sharpe fired another salvo. "A pointer might be a built-in pointer, an auto_ptr or a shared_ptr? And a function might be a member function, a non-member function or a function object?" He wasn't giving me time to think and I sensed he was preparing a trap. I responded with a cautious, "In principle, yes."

There was a pause while Sharpe presented Exhibit 4 to the court.

// A bound callback that calls a member function.
template<typename Pmf>
class Bound_Callback
  : public Callback::Function<typename argument<Pmf>::type>
{
public:
    typedef typename   result<Pmf>::type Result;
    typedef typename   class_<Pmf>::type Class;
    typedef typename argument<Pmf>::type Arg;

    Bound_Callback(Event<Arg>& e, Pmf f, Class* p)
      : event(e), function(f), pointer(p),
        position(event.insert(event.end(), this))
    {}

   ~Bound_Callback() {event.erase(position);}

    virtual Result operator()(Arg arg)
    {
        return (pointer->*function)(arg);
    }

private:
    Event<Arg>& event;
    Pmf         function;
    Class*      pointer;

    typename Event<Arg>::iterator position;
};

Exhibit 4 - A bound callback for member functions.

"Now, Detective Inspector, I am sure you recognise this piece of code." Sharpe placed a sheet of paper in front of me and continued, "It is the code you wrote to demonstrate that a bound callback with a user-friendly interface can be implemented, is it not?" I nodded and Sharpe pressed on before the judge could remind me to answer so that the court could hear.

Turning to the jury he explained that the test program for Exhibit 4 used an Event<int> that is a list of built-in pointers to the abstract base class Callback::Function<int> . Then, turning back to me, he asked "Does this code work for Events that are sets of pointers to functions?" "No" I replied steadily. "No? Well then, does it work for arrays of pointers to functions?" I did not respond. "Does it work for lists of smart pointers to functions? Does it work for lists of pointers to ordinary C++ functions, or function objects not derived from Callback::Function<int> ? Does it work in any of these situations, Detective Inspector?" I started to say that it was never intended to be that general, but Sharpe cut me off. "Yes or no" he demanded. I looked to the prosecution team for support, but they didn't stir. I turned towards the judge to appeal against this line of questioning, but he was entirely unsympathetic. "Yes or no, Detective Inspector Blunt?" thundered Sharpe. "No, it doesn't support those uses." I confessed.

A Brief Interlude

I was livid. It looked as though Joe Coder might get off on a technicality. More importantly, I had been made to look a proper Charlie. At the next recess I called the team together and demanded to know why no-one had anticipated Sharpe's under-hand tactics. There were no answers. There were plenty of sheepish faces and lots of excuses, but no answers.

There was an awkward silence. A young police constable tried to cheer me up. "Sir?" I turned and raised an eyebrow, inviting her to continue. "Sharpe was talking about auto_ptr and boost::shared_ptr - smart pointers that manage the lifetime of their target object." I didn't see how this was helping. "Well, sir, that makes no sense. The Bound_Callback<Pmf> classes are designed to be members of the Observer class, so their lifetime is tied to the Observer 's. And, anyway, you can't put auto_ptr s in standard library containers." "I know that, Jenkins", I snapped, "you, know that and Sharpe certainly knows that, but do you think the jury realises it?"

The Judge's Summing Up

Justice Bright is renowned for his clear and insightful summing up. He reminded the jury that the charge was coding without due care and attention and with reckless disregard to the welfare of other code users. "In this case, the intentions of the defendant are crucial." he explained. "If he believed his fellow programmers would be best served by generic, but somewhat verbose facilities for bound callbacks you must find him not guilty. If, on the other hand, he thought that a more limited, but more user-friendly facility was better you must find him guilty as charged. In reaching your verdict you do not need to consider the defendant's competence as a programmer. Better and worse solutions to the bound callback problem undoubtedly exist, but we can not condemn a man for failing to reach perfection nor is it the responsibility of this court to apply remedies for lack of ability or training."

The Jury Retires

The judge's words were going round in my head as we waited for the jury to consider their verdict. The case would be decided by the jury's assessment of the defendant's character, but the technical question behind the trial remained unresolved. Exhibit 1 illustrates a general, but verbose mechanism for implementing bound callbacks; Exhibit 2 shows a more limited, but easier-to-use alternative. Could there be a bound callback that is both fully generic and easy to use?

I tried to imagine what that perfect bound callback would look like, but it was too big a problem to solve all at once.

Then I considered some specific Event types:

  1. a list of pointers to abstract function objects (the common case),

  2. an array of pointers to simple functions (the simplest case) and

  3. a set of shared pointers to function objects (associative container + user-defined pointer).

What would callbacks bound to these types of Event have in common? How would they differ? And how would they invoke handler functions of any compatible type? I fell into deep thought…

In case 1 the bound callback has to provide a concrete implementation of the abstract function, its constructor should insert a new pointer into the list and its destructor should erase the pointer from the list. This is a generalisation of the code shown in Exhibit 4. Sketch 1 illustrates the idea.

template<typename Event, typename Function>
class Bound_Callback
{
public:
    Bound_Callback(Event& e, const Function& function)
      : event(&e)
      , adapter(function)
      , position(event->insert(event->end(), &adapter))
    {}

    ~Bound_Callback() {event->erase(position);}

private:
    Event*                                 event;
    typename adapter<Event,Function>::type adapter;
    typename iterator<Event>::type         position;
};

Sketch 1 - A bound callback for a list of pointers to abstract functions.

Here, adapter is a meta-function that generates the type of a concrete function object and iterator is a meta-function that generates the list iterator type. This much was clear to me while waiting for the jury's verdict; the precise details could be worked out later.

Case 2 is very different. No function object is necessary and arrays do not support insert or erase operations. Instead, the callback's constructor could assign a function pointer to an array element and the destructor could reset that array element to a pointer to a no-op function. This would look something like Sketch 2:

template<typename Position >
class Bound_Callback
{
public:
    typedef typename   target<Position>::type Pointer;
    typedef typename   target<Pointer >::type Function;
    typedef typename   result<Function>::type Result;
    typedef typename argument<Function>::type Arg;

    static Result no_op(Arg) {}

    Bound_Callback(Position p, const Function& f)
      : position(p)
    {
        *position = &f;
    }

    ~Bound_Callback() {*position = no_op; }

private:
    Position position;
};

Sketch 2 - A bound callback for an array of pointers to functions.

Although they are small class templates there are several differences between Sketches 1 and 2. In particular, the template parameter lists are completely different. The data storage requirements and the constructor parameters are different, too. In fact the differences are large enough to call into question the whole idea of a single fully generic bound callback.

In case 3 a concrete function object may need to be created, the constructor would create a shared pointer and insert it into the set, and the destructor would erase the shared pointer.

template<typename Event, typename Function>
class Bound_Callback
{
public:
    typedef typename element<Event>::type Pointer;

    Bound_Callback(Event& e, const Function& f)
      : event(&e),
        position(event->insert(make_pointer<Pointer>(f)))
    {}

    ~ Bound_Callback() {event->erase(position);}

private:
    Event*                         event;
    typename iterator<Event>::type position;
};

Sketch 3 - A bound callback for a set of shared pointers to abstract functions.

This is similar to Sketch 1 except that a function object is created on the heap; the make_pointer function template creates the function object and returns a shared pointer to the new object. An intelligent make_pointer function could select an intrusive shared pointer if the user supplies a reference-counted function object, or a non-intrusive shared pointer otherwise.

In all three cases the bound callback might need to provide a function adapter to convert the value published by the Event to the type required by the user-supplied handler function. Sketches 1 and 3 already use a function adapter, so it can be extended for this purpose. In Sketch 2, a function adapter would have to be introduced specially. I began to think about merging these sketches into a single, truly generic bound callback. The three possibilities took on geometric shapes in my mind. They seemed to float before me, drifting around like globules of oil in a lava lamp, rising, falling, merging, splitting. It was relaxing, soothing.

Verdict

I woke up with a start. My sergeant's hand was shaking my shoulder. "Inspector, Inspector", he was saying. "They've reached a verdict." I roused myself and went back into the courtroom. The judge and jury filed in. The clerk of the court went through the ritual of asking if the members of the jury were all agreed. They were. "Do you find the defendant guilty or not guilty?" Perhaps it was my imagination, but the foreman of the jury seemed to pause for dramatic effect and then said in a clear and confident voice, "Not guilty."

So that was it. The prosecution I'd worked hard on for most of the last 6 months had failed to achieve a conviction. It seemed a waste. It wasn't a big case, but it had given my team a purpose, a reason to press on through the humdrum routine of police work, a feeling of doing something worthwhile. "Oh well, you win some, you lose some." I told myself and turned to leave.

Acknowledgements

I would like to thank Mary Opie for suggesting the names Blunt and Sharpe.

References

[Bass] Phil Bass, "The Curious Case of the Compile-Time Function", Overload 62 , August 2004.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.