C/C++ Contributing Editors


Uncaught Exceptions: 21st Century Man

Bobby Schmidt

It may be the new millennium, but we still have trouble understanding switch statements, templates, access modifiers, and (in some folks’ view) how to keep things simple.


Copyright © 2000 Robert H. Schmidt

Oh Joy, Oh Bliss: it’s time for the January 2001 issue. Once the new year starts, we can finally all agree that the 21st century is at hand. No more "neener neener neener, my millenium’s more scientifically accurate than yours" playground taunts.

In my sci-fi youth, I became influenced by several visions of early 21st century life. Among them:

And then there was ELO. For reasons I’ll save for another time, their 1981 album Time is an intimate part of my life’s soundtrack. The theme of the album — one person’s perceived journey from from the 80s to the 21st century and back again — played heavily upon my imagination. It’s still one of my all-time favorite albums [1].

const Is Not Enough

Q

Dear Schmidt,

Is it possible (by some casting magic or other form of black art) to make the following code compile:

char c_case;
cin >> c_case;

char c;
cin >> c;

switch (c)
    {
    case c_case: // error here
        // ...
    }

I have tried const_cast<char const>(c_case) and all kind of other variations. From what I have read, const_cast should do the job but it’s not working using either Visual C++ 6.0 or C++ Builder 5.0.

Any hint, suggestion or (YES!) the solution if any would be greatly appreciated...

Your truly — Tibor Hellvig

A

Dear Hellvig,

Sorry to disappoint you with bad news: your desired solution won’t work. The case labels in a switch statement must be what the C++ Standard deems integral constant expressions [2]. The compiler can calculate such expressions as it compiles the program — that is, before the program is actually run. For a switch statement, this allows the compiler to embed the case values directly into the generated code, as either instructions or a jump table.

In your example, c_case’s value won’t be known until the program executes. Casting c_case to const_char won’t help, because the compiler still won’t know the resulting value, const though it may be. This marks a clear difference between the switch statement and its equivalent if statement, which would allow:

if (c == c_case)

I’ll also note that, independent of case label considerations, your proposed syntax:

const_cast<char const>(c_case)

won’t work in any context. The closest you can come is:

const_cast<char const &>(c_case)

T Time

Q

Bobby,

I would like to create a log class that writes to a flat file and optionally to some auxiliary device like a window, status bar, etc. The class will then have some method like

class log
    {
public:
    void setAuxDevice(void (*pf)
        (string const &data, void *device));
    };

After looking this over and thinking about it I decided that templates might be a good way to get rid of the void *. Is there a way to pass a pointer to a templated function? And secondly, in doing so can you maintain non-type specificity? By this I mean:

// the type is reference to vector of ints
void setVal(vector<int> &);

// non-type specificity
void setVal(vector<T> &);

Thanks — Chuck Emary

A

If I’m reading your question right, the answer is no, unless you are willing to change your design somewhat.

Two fundamental issues undermine your approach:

In your first example, I think you want something like

class log
    {
    void setAuxDevice(void (*pf)
        (string const &data, T device));
    };

where T is a template parameter of some entity declared outside log. The only way this would work is if you declared log within some other template taking T as a parameter:

template<typename T>
class X
    {
    // ...
    class log
        {
        void setAuxDevice(...);
        };
    };

X<window>::log     log1;
X<status_bar>::log log2;

In that case, T would be known to both log and setAuxDevice.

As alternatives, you could make either log a template:

template<typename T>
class log
    { void setAuxDevice(...); };

log<window>     log1;
log<status_bar> log2;

or setAuxDevice a template:

class log
    {
    template<typename T> static void setAuxDevice(...);
    };

log l;

Regardless of your approach, T must be declared as a template parameter within the context of setAuxDevice.

In your second example:

void setVal(vector<int> &);

void setVal(vector<T> &);

you apparently want to overload setVal, so that the specialized version takes a vector of int, the general version takes a vector of everything else, and no version takes a non-vector.

As before, T is a template parameter, and must be declared as such in the context of the second setVal. The most direct solution is

void setVal(vector<int> &);

template<typename T> void setVal(vector<T> &);

Now given

vector<char> vc;
vector<int> vi;
int i;

the calls

setVal(vc); // OK, general
setVal(vi); // OK, specialized
setVal(i);  // error

work as you expect.

Private vs. Secret

Q

My friend is worried that anyone can access private members of his class OldBank by using pointers. He asked me for a solution, so I changed his class to NewBank. My problem: we can still access the private member by any means. — Anurag Uniyal

A

I’ve simplified your classes as Listing 1 and your problem demonstration as Listing 2.

You are experiencing two difficulties:

At the risk of redundancy, I’ll repeat advice I’ve seen in the C++ newsgroups many times: private is not equivalent to secret. Access control (such as private) prevents accidents, not malice. It is certainly not intended as a robust security measure.

C++ access control simply puts a type-safe face on a plain old C struct. Once you bypass that type safety — as you’ve done with your (int *) type cast — you have no more protection in C++ than you have in C. If you truly want to keep prying eyes away from your data, you must hide it through a secure means that works independently of your programming language — a topic far beyond my expertise.

Erratica

Several Diligent Readers have taken me to task for my template implementation of Java’s super keyword [3]. Hans Salvisberg puts it this way:

"I usually like your answers, but your use of templates for simulating Java’s super keyword is too much code for a simple problem, and it obfuscates the class hierarchy."

David Doty was slightly softer:

"I recently started reading CUJ after a lapse of <mumble> years in the Delphi world, and really enjoy your column. But (you knew this was coming, didn’t you?), your template-based implementation of the Java super keyword (a.k.a. inherited in the Delphi world) seems to be perhaps overkill."

(And just when Andrei Alexandrescu finally trained me that templates were the balm for all C++ ills...)

They and other readers suggest variations of a solution I had already considered: defining a private typedef super within the deriving class that is aliased to the base class. In the context of the original problem, such a solution would allow

class Top
    {
public:
    virtual void f();
    };

class Bottom : public Top
    {
private:
    typedef Top super;
public:
    virtual void f()
        {
        super::f();
        }
    };

This solution works fine, and is simpler than mine. The problem comes when you follow the original questioner’s scenario of inserting a class Middle between Top and Bottom:

class Middle : public Top

With the non-template solution, you must remember to make two changes to Bottom:

class Bottom
    : public Middle // was Top
    {
private:
    typedef Middle super; // was Top
    // ...
    };

Requiring these changes is an accident waiting to happen. Indeed, the original questioner was motivated to write me because he forgot to make the equivalent changes in his code, and wanted a mechanism that was self-maintaining.

His desire lead me to the template solution. I reasoned that requiring one change, from

Bottom_<Top>

to

Bottom_<Middle>

was more robust than requiring two discrete changes.

Does that robustness come at a complexity cost? Absolutely. Is it the "best" solution in all contexts? Nope. Is it less error-prone? I’d argue yes, although others clearly would argue differently.

Indeed, based on the response I’ve received, I seem to be the only one who finds virtue in the template solution. Even Bjarne’s against me: as David Tribble points out, Stroustrup recommends the non-template solution in his Design and Evolution of C++ (section 13.6).

Hmmm.

Okay, how about this: we’re both right! After all, both solutions will work (albeit with different ancillary costs and benefits).

One of C++’s prime strengths is its adaptability. The language often offers multiple solutions to a given problem. That we can argue about which solution is best — rather than whether a solution exists at all — is testament to that adaptability.

In my columns, I offer possibilities and alternatives. I don’t claim them as the Alpha and Omega of C++ design. Sometimes I’ll recommend solutions consistent with mainstream ideology. And sometimes — as is the case here — I’ll recommend solutions that strike people as odd, or overly complicated, or just plain wrong.

In the end, I’m trying to get people thinking about their own approaches. If making an offbeat recommendation stirs people’s minds enough to respond, then I think I’ve done a large part of my job. (Besides, by apparently overlooking such an "obvious" solution, I discover who’s really paying attention!)

Notes

[1] Yes, I do believe this qualifies as a Guilty Pleasure.

[2] C++ Standard subclause 6.4.2.

[3] Item "Super Trouper" in my September 2000 column.

Although Bobby Schmidt makes most of his living as a writer and content strategist for the Microsoft Developer Network (MSDN), he runs only Apple Macintoshes at home. In previous career incarnations, Bobby has been a pool hall operator, radio DJ, private investigator, and astronomer. You may summon him on the Internet via BobbySchmidt@mac.com.