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: its 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 milleniums 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:
- 2001: A Space Odyssey (set in 2001). I remember being on family vacation when this movie came out. We used to eat at HoJos a lot on the road; the restaurant had marketing ties with the movie producers. The parts of the film involving the space station gave me great hope, since I really wanted to travel to space and to the moon. But all the parts involving the monolith made no sense to me as a kid.
- Rollerball (2018). Grisly look and concept that really grabbed me (although Ive since seen the movie as an adult, and found the storyline inept). Even though the movie was rated R, a lot of us boys in junior high wanted to see it. The soundtrack fostered my love of thundering organ music. MGM is currently filming a remake.
- Blade Runner (2019). When this came out, I could almost imagine Los Angeles looking like the movie in four decades. Otherwise, I had trouble suspending disbelief Flying cars? Androids indistinguishable from humans? Colonization of other star systems? All that notwithstanding, I still adore this flick.
- Sealab 2020 (2020). Dead stupid Saturday morning cartoon. Its only redeeming factor: stirring my interest in future undersea living. At the time, I didnt understand the scientific, economic, and social consequences of humans inhabiting domed cities on the ocean floor. I just thought it was a cool idea and wanted to be part of it.
- Soylent Green (2022). As a child highly influenced by Paul Ehrlichs The Population Bomb, I found this movie quite disturbing so much so that I calculated how old Id be in the year it was set. To my horror, I reckoned Id be little younger than Edward G. Robinsons character.
And then there was ELO. For reasons Ill save for another time, their 1981 album Time is an intimate part of my lifes soundtrack. The theme of the album one persons perceived journey from from the 80s to the 21st century and back again played heavily upon my imagination. Its 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 its 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 wont 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_cases value wont be known until the program executes. Casting c_case to const_char wont help, because the compiler still wont 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)Ill also note that, independent of case label considerations, your proposed syntax:
const_cast<char const>(c_case)wont 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 Im reading your question right, the answer is no, unless you are willing to change your design somewhat.
Two fundamental issues undermine your approach:
- T is a template parameter. Anything referencing T must itself be part of the template declaring T.
- A function template is a potential family of functions, not a single function. You cant declare a pointer to a template.
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); // errorwork 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
Ive simplified your classes as Listing 1 and your problem demonstration as Listing 2.
You are experiencing two difficulties:
- By judicious casting, users are able to fetch the supposedly private bits of OldBank. You want to prevent this.
- Your proposed solution (NewBank) removes that problem while creating another. OldBank maintains a distinct pass state for every OldBank object, while NewBank shares the same pass state among every NewBank object. I somehow doubt this is your intent.
At the risk of redundancy, Ill repeat advice Ive 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 youve 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 Javas super keyword [3]. Hans Salvisberg puts it this way:
"I usually like your answers, but your use of templates for simulating Javas 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, didnt 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 questioners scenario of inserting a class Middle between Top and Bottom:
class Middle : public TopWith 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? Id argue yes, although others clearly would argue differently.
Indeed, based on the response Ive received, I seem to be the only one who finds virtue in the template solution. Even Bjarnes 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: were 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 dont claim them as the Alpha and Omega of C++ design. Sometimes Ill recommend solutions consistent with mainstream ideology. And sometimes as is the case here Ill recommend solutions that strike people as odd, or overly complicated, or just plain wrong.
In the end, Im trying to get people thinking about their own approaches. If making an offbeat recommendation stirs peoples minds enough to respond, then I think Ive done a large part of my job. (Besides, by apparently overlooking such an "obvious" solution, I discover whos 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.