It's of course just placeholder. |
Yes, clearly. The problem is that you simply cannot compress any random string to an integer without loosing information — meaning you will have collisions.
If you have a
specific use case where there are very strict restrictions on the strings allowed, then you can design a hash which will not collide. (Google around “perfect hash”.)
The problem is guaranteeing that the strings are part of the perfect hash.
This might be worth some play time. :^J
but colleagues of mine aren't too hot on associative containers and function pointers |
Your colleagues are not
smart good programmers.
Programmers with religious hobbies or always/never rules or gut feelings are the kinds that screw your code base over.
Associative arrays are one of — if not
the — most important and influential data structures in all of computer programming history. Eschewing them for <unknown reasons> is foolish.
An array of function pointers is no more dangerous or weird than dynamic dispatch, one of the central characteristics of OOP.
Hence the "no clutter" approach |
IMNSHO that is bullcrap language your colleagues have brainwashed into you. It is a logically-dubious way of hand-waving away “don’t want, don’t care”.
In programming parlance, “clutter” has a very specific meaning:
http://wiki.c2.com/?LanguageIdiomClutter. For less technical purposes, it is an antonym of
clean|
http://catb.org/jargon/html/C/clean.html
In both cases, the idea is that
unnecessary things are being introduced into a design, making it not simple/elegant/whatever inexact metric one wants to use.
However, I suspect that what your colleagues mean is: “don’t introduce extra functions I have to look up when a local block will do.”
I personally think the jump table looks cleaner, and matches a lot of my switch statement use anyway:
1 2 3 4 5 6 7
|
switch (x)
{
case A: do_one(); break;
case B: do_two(); break;
case C: do_three();
}
|
std::map <X, std::function <void()> > action
{
{ A, do_one },
{ B, do_two },
{ C, do_three },
};
if (action.count( x )) action[x](); | |
Those both look very nice and clean to me.
─────────────────────────────────────────────────────────────────
I was about to hit “Submit” but then figured I could whip up something interesting, showing that it is entirely possible and cleanly efficient to make a “non-clutter” (however you want to define that) string switch. So... here it is:
1 2 3 4 5 6 7 8 9 10 11 12
|
#include <functional>
#include <string>
#include <unordered_map>
#define sswitch(s) { std::string _s = s; bool _continue = false; std::unordered_map <std::string, std::function <void()> > ss {
#define scase(s) }, { s, [&]()
#define sdefault }, { "\1", [&]()
#define sbreak return
#define scontinue _continue = true; return
#define sgoto(s) if (ss.count( s )) {ss[s](); _s = ""; sbreak; }
#define sgoto_default sgoto("\1")
#define end_sswitch }; if (ss.count( _s )) ss[_s](); else if (ss.count( "\1" )) ss["\1"](); if (_continue) continue; }
| |
This looks and behaves very much like a standard switch statement, but does a proper switch(string) without mis-matches.
There are only three significant differences:
• There is no automatic fall-through.
Use
sbreak only to escape early.
Use
sgoto to perform what fall-through would accomplish.
• To continue on the next iteration of a loop, you must use
scontinue.
• You must use
end_sswitch to terminate the switch statement.
This is a consequence of RLM (radical language modification).
Since this is a compile-time structure you even have the option to create a
constexpr perfect hash with it. Replace the
std::unordered_map with something appropriate and verify that the matched value actually matches your string in the
end_sswitch test. (Google around “C++ perfect hash map” for things people have written.)
Finally, an example. Notice how closely it mirrors an integer
switch.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
|
#include <algorithm>
#include <cctype>
#include <chrono>
#include <ciso646>
#include <iostream>
#include <limits>
#include <string>
#include <thread>
#include "string-switch.hpp"
int main()
{
bool done = false;
std::cout <<
"Welcome to the MENU program.\n"
"First-letter abbreviations are OK. Whitespace and letter case are ignored.\n";
while (!done)
{
std::cout <<
"\nMENU\n"
"eat\n"
"sleep\n"
"derp\n"
"quit\n"
"? ";
std::string s;
getline( std::cin, s );
std::cout << "\n";
// Remove whitespace
s.erase( std::remove_if( s.begin(), s.end(), []( char c ) { return std::isspace( c ); } ), s.end() );
// tolower( s )
for (char& c : s) c = std::tolower( c );
sswitch (s)
{
scase("eat")
{
std::cout <<
"The Elvis:\n"
"Peel and slice a banana into 1/4\" pieces.\n"
"Apply liberally to a Peanut Butter sandwich.\n"
"Enjoy!\n";
}
scase("sleep")
{
std::cout << "how long (seconds)? ";
int n;
(std::cin >> n).ignore( std::numeric_limits <std::streamsize> ::max(), '\n' );
if ((n < 0) or (n > 5))
{
std::cout << "No sleep for you!\n";
sbreak;
}
std::cout << "sleeping..." << std::flush;
std::this_thread::sleep_for( std::chrono::seconds( n ) );
std::cout << "...awake!\n";
}
scase("derp") { sgoto_default; }
scase("quit") { done = true; scontinue; }
scase("e") { sgoto("eat"); }
scase("s") { sgoto("sleep"); }
scase("d") { sgoto("derp"); }
scase("q") { sgoto("quit"); }
sdefault { std::cout << "Um, not an option."; }
}
end_sswitch
std::cout << "\nTry another one!\n";
}
std::cout << "Good bye!\n";
}
| |
Enjoy!