Refactoring needed for repetitious switch statements - c++

The many repetitious switch statements seems like it needs to be DRY'd. Any suggestions? (Including doing nothing!)
AnimMapIter _iter;
_iter = _animations->find(name);
if(_iter == _animations->end()) return;
if(_curName != name) {
_curName = name;
switch(dir) {
case DIR_FORWARD_LOOPING: /* Fall through to DIR_FORWARD_NONLOOPING */
case DIR_FORWARD_NONLOOPING:
_iter->second->First();
break;
case DIR_REVERSE_LOOPING: /* Fall through to DIR_REVERSE_NONLOOPING */
case DIR_REVERSE_NONLOOPING:
_iter->second->Last();
break;
}
} else {
switch(dir) {
case DIR_FORWARD_LOOPING: /* Fall through to DIR_FORWARD_NONLOOPING */
case DIR_FORWARD_NONLOOPING:
_iter->second->Next();
break;
case DIR_REVERSE_LOOPING: /* Fall through to DIR_REVERSE_NONLOOPING */
case DIR_REVERSE_NONLOOPING:
_iter->second->Previous();
break;
}
switch(dir) {
case DIR_FORWARD_LOOPING:
if(_iter->second->IsAtEnd())
_iter->second->First();
break;
case DIR_FORWARD_NONLOOPING:
if(_iter->second->IsAtEnd())
_iter->second->Last();
break;
case DIR_REVERSE_LOOPING:
if(_iter->second->IsAtFront())
_iter->second->Last();
break;
case DIR_REVERSE_NONLOOPING:
if(_iter->second->IsAtFront())
_iter->second->First();
break;
}
}

Everything under the else should collapse into a single switch to bring the related steps closer; e.g.
case DIR_FORWARD_LOOPING:
_iter->second->Next();
if (_iter->second->IsAtEnd()) {
_iter->second->First();
}
break;
...all in that one case. Repetition of a couple of function calls is not a big deal when it makes the overall sequence of actions more clear.

Push the logic into whatever _iter->second is, along these lines (assuming the methods you've already shown exist):
class WhateverItIs
{
public:
void Start() { if (m_forward) First(); else Last(); }
void Stop() { if (m_forward) Last(); else First(); }
void Advance()
{
if (m_forward)
Next();
else
Previous();
if (IsLast())
{
if (m_loop)
Start();
else
Stop();
}
}
private:
bool IsLast() const
{
return m_forward ? IsAtEnd() : IsAtFront();
}
// Direction and looping are independent concepts.
bool m_forward;
bool m_loop;
};
Then you can write:
AnimMapIter _iter;
_iter = _animations->find(name);
if(_iter == _animations->end()) return;
if(_curName != name) {
_curName = name;
_iter->second->Start();
} else {
_iter->second->Advance();
}
Edit: Example using free functions and keeping the constants.
void Start(Strip* s, bool forward)
{ if (forward) s->First(); else s->Last(); }
void Stop(Strip* s, bool forward)
{ if (forward) s->Last() else s->First(); }
void Advance(Strip* s, bool forward, bool loop)
{
if (forward)
s->Next();
else
s->Previous();
if (IsLast(s, forward))
{
if (loop)
Start(s);
else
Stop(s);
}
}
bool IsLast(const Strip* s, bool forward) const
{
return forward ? s->IsAtEnd() : s->IsAtFront();
}
bool Projector::IsForward() const
{
return dir == DIR_FORWARD_LOOPING || dir == DIR_FORWARD_NONLOOPING;
}
bool Projector::IsLooping() const
{
return dir == DIR_REVERSE_LOOPING || dir == DIR_FORWARD_LOOPING;
}
if(_curName != name) {
_curName = name;
Start(_iter->second, IsForward());
} else {
Advance(_iter->second, IsForward(), IsLooping());
}

Related

How to do a variadic macro and or template and or function that captures the expression as well as its evaluation?

I have overloaded a class with various << operators
inline QString operator<<(bool boolean) {
return (boolean ? QString("true") : QString("false"));
}
inline QString operator<<(char const *string) {
return QString(string);
}
inline QString operator<<(int number) {
return QString::number(number);
}
Basically what I want to do, is write a debug output that captures the expression, like so:
#define DEBUG(...)
DEBUG(QString("foobar"), someObject.toInt());
// Expression 1: """QString("foobar")"""
// Expression 2: """someObject.toInt()"""
And combines it with its evaluation:
#define DEBUG(...)
DEBUG(QString("foobar"), someObject.toInt());
// Evaluation 1: "foobar"
// Evaluation 2: "1234"
Prepending all with
__FILE__;
QString::number(__LINE__);
__PRETTY_FUNCTION__;
And it should output something like this:
#define DEBUG(...)
DEBUG(QString("foobar"), someObject.toInt());
///////////////// Output /////////////////
File: /home/akiva/Programming/MyApp/source.cpp
Line: 123
Func: void doSomething();
QString("foobar")
"foobar"
someObject.toInt()
"1234"
I am having trouble doing this, as doing recursive variadic macros is not exactly legal. I am also having some additional difficulty, as my previous method of using variadic templates coupled with std::forward, is not working in web assembly as far as I can tell. Ideally the solution once tested will also be compatible with Wasm.
Thanks.
I figured it out by building a small parser to parse the initial expression. The parser is bound to have bugs, as I just pieced it together over an hour.
Inside a class:
static void _Continue(QStringList &sl)
{
QString s;
{
s.append(sl.takeFirst()); // File
s.append(":");
s.append(sl.takeFirst()); // Line
s.append("<br> "); // Outputting to a text box.
s.append(sl.takeFirst()); // Func
}
/* Parse Expression */
QStringList args;
{
QString expr(sl.takeFirst()); // Expression
bool isQuote (false); // ""
bool isLiteral(false); // ''
bool isPointer(false); // ->
int roundLevel(0); // ()
int curlyLevel(0); // {}
int squarLevel(0); // []
int angleLevel(0); // < > // Fixme handle <<, >>, <= etc
expr.remove(QRegularExpression("[/][*].*[*][/]")); // Remove Comments
QString arg;
QRegularExpression re_CommaWhitespaceAtBeginning("^[, \t\n]*");
while (!expr.isEmpty()) {
if (isPointer && expr.front().unicode() != u'>') { isPointer = false; }
switch (expr.front().unicode()) {
case u'-' : {
isPointer = true; // potentially
break;
}
case u'"' : {
isQuote = !isQuote;
break;
}
case u'\'': {
isLiteral = !isLiteral;
break;
}
case u'(' : {
if (isQuote) { break; }
roundLevel++;
break;
}
case u')' : {
if (isQuote) { break; }
roundLevel--;
break;
}
case u'[' : {
if (isQuote) { break; }
squarLevel++;
break;
}
case u']' : {
if (isQuote) { break; }
squarLevel--;
break;
}
case u'{' : {
if (isQuote) { break; }
curlyLevel++;
break;
}
case u'}' : {
if (isQuote) { break; }
curlyLevel--;
break;
}
case u'<' : {
if (isQuote) { break; }
angleLevel++;
break;
}
case u'>' : {
if (isQuote || isPointer) {
isPointer = false;
break;
}
angleLevel--;
break;
}
case u'\\': {
if (isQuote) { arg.append(expr.front()); }
break;
}
case u',' : {
if (isQuote
|| isLiteral
|| roundLevel
|| curlyLevel
|| squarLevel
|| angleLevel) { break; }
args << arg.remove(re_CommaWhitespaceAtBeginning);
arg.clear();
break;
}
default : {
}
}
arg.append(expr.front());
expr.remove(0,1);
}
args << arg.remove(re_CommaWhitespaceAtBeginning);
}
// sl should now have only values;
// args should be shorter than sl, even with error
for (int i = 0; !args.isEmpty(); i++) {
sl.replace(i, args.takeFirst() + "<br/>" + sl.at(i));
}
sl.prepend(s);
/* Messages stored on a map, signal sent to update it. */
MainWindow::s_StdoutMap.append(sl.join("<br/><br/>"));
emit MainWindow::s_MW->on_actionUpdate_triggered();
}
template<typename type, typename ... T>
static void _Continue(QStringList &sl, type t, T ... args)
{
sl << (*MainWindow::s_MW << t); // overloaded << operator to handle T
MainWindow::_Continue(sl, args ...);
}
template<typename ... T>
static void _Start(QString file, int line, QString func, QString expr, T ... args)
{
QStringList sl;
{
sl << file;
sl << QString::number(line);
sl << func;
sl << expr;
}
MainWindow::_Continue(sl, args ...);
}
#define ct_Debug(...)MainWindow::_Start(__FILE__, __LINE__, __PRETTY_FUNCTION__,\
#__VA_ARGS__, __VA_ARGS__)
And it gives me an output along the lines of this:
If you are wondering why it isnt in the console, as said, this is for web assembly.

How can I tell the compiler to use my overloaded operator instead of using the "normal" operator?

Here I have the enum class:
enum class wahl {
schere , stein , papier
};
And then I overload the operator < and >
bool operator<(wahl &wahl1, wahl &wahl2) {
switch (wahl1) {
case wahl::papier: {
if (wahl2 == wahl::schere) {
return true; break;
} //papier < schere
else if (wahl2 == wahl::stein) {
return false; break;
} //papier > stein
else {
return false; break;
}
}
case wahl::schere: {
if (wahl2 == wahl::stein) {
return true; break;
} //schere < stein
else if (wahl2 == wahl::papier) {
return false; break;
} //schere > papier
else {
return false; break;
}
}
case wahl::stein: {
if (wahl2 == wahl::papier) {
return true; break;
} //stein < papier
else if (wahl2 == wahl::schere) {
return false; break;
} //stein > schere
else {
return false; break;
}
}
}
};
bool operator > (const wahl wahl1, const wahl wahl2) {
switch (wahl1) {
case wahl::papier: {
if (wahl2 == wahl::schere) {
return false; break;
}
// papier < schere
else if (wahl2 == wahl::stein) {
return true; break;
} //papier > stein
else {
return false; break;
}
}
case wahl::schere: {
if (wahl2 == wahl::stein) {
return false; break;
} //schere < stein
else if (wahl2 == wahl::papier) {
return true; break;
} //schere > papier
else {
return false; break;
}
}
case wahl::stein: {
if (wahl2 == wahl::papier) {
return false; break;
} //stein < papier
else if (wahl2 == wahl::schere) {
return true; break;
} //stein > schere
else {
return false; break;
}
}
}
};
I have another class, named player :
class player {
wahl pl_wahl;
int pl_score;
char* pl_name;
public:
player() {}
player(int score, wahl wahl, char* name) :
pl_wahl{ wahl }, pl_score{ score }, pl_name{ name } {}
wahl pl_get_wahl() {
return pl_wahl;
}
char* pl_get_name() {
return pl_name;
}
int &pl_get_score() {
return pl_score;
}
};
And here where I used the comparator :
class game {
player game_player1, game_player2, game_momentan_gewinner;
int game_score_max;
public:
game() {}
game(player player1, player player2, int score_max) :
game_player1{player1},
game_player2{player2},
game_score_max{ score_max } {}
void vergleichen() {
if (game_player1.pl_get_wahl() > game_player2.pl_get_wahl()) {
game_momentan_gewinner = game_player1;
std::cout << "Gewinner dieser Runde ist Player 1 : " <<
game_momentan_gewinner.pl_get_name() << std::endl;
}
if (game_player1.pl_get_wahl() < game_player2.pl_get_wahl()) {
game_momentan_gewinner = game_player2;
std::cout << "Gewinner dieser Runde ist Player 2 : " <<
game_momentan_gewinner.pl_get_name() << std::endl;
}
else {
std::cout << "Remis" << std::endl;
}
}
};
The problem that I have, is enum will be proceed as int. And if I ask the compare two enum variables, the result will depend on the int value rather than taking the value, that I have set with the overloaded operator.
Is there any way, that i can stop the compiler to use the int value of the enum variables, and compare the enum variables in the way I want like in the overloaded operator?
If you use correct signature, it should be ok:
bool operator<(wahl lhs, wahl rhs);
bool operator>(wahl lhs, wahl rhs);
Demo

error: no matching function for call to c++ enumeration

I have the following problem already longer time. The point ist that I have read a bit on stackoverflow and I have used a typedef, but I doesnt help me.
Looking forward to get some help :)
Align_vector.h :
#include <utils/vector_2d.h>
class Vector_2d;
namespace Utils {
class Align_vector : public Vector_2d {
protected:
bool check_range(int x, int y);
public:
typedef enum {left, right, up, down} Alignment;
Align_vector(Alignment alignment);
void set_alignment(Alignment alignment);
Alignment get_alignment();
};
} /* namespace Utils */
Align_vector.cc :
#include <utils/align_vector.h>
namespace Utils {
Align_vector::Align_vector(Alignment alignment) {
this->alignment = alignment;
}
void set_alignment(Alignment alignment) {
Align_vector::alignment = alignment;
}
Alignment get_alignment() {
return Align_vector::alignment ;
}
bool check_range(int x, int y) {
switch ( Align_vector::alignment ) {
case Align_vector::left:
if (x == -1 && y == 0) {
return true;
} else {
return false;
}
break;
case Align_vector::right:
if (x == 1 && y == 0) {
return true;
} else {
return false;
}
break;
case Align_vector::down:
if (x == 0 && y == -1) {
return true;
} else {
return false;
}
break;
case Align_vector::up:
if (x == 0 && y == 1) {
return true;
} else {
return false;
}
break;
default:
return false;
break;
}
}
} /* namespace Utils */
Here is the error:
/utils/align_vector.cc:14:47: error: no matching function for call to ‘Utils::Vector_2d::Vector_2d()’
Align_vector::Align_vector(Alignment alignment) {
This code has numerous issues. First of all, you have defined an
enum called Alignment, but you did not declare a member
of that type. To do so, add this line after the definition of the enum:
Alignment alignment;
Your definitions of methods are also incorrect, the alignment is
supposed to belong to a specific object, while you are using it in
several functions as if it were a static member of the class. Here
are the fixed versions of method definitions:
namespace Utils {
Align_vector::Align_vector(Alignment alignment) {
this->alignment = alignment;
}
void Align_vector::set_alignment(Alignment alignment) {
this->alignment = alignment;
}
Align_vector::Alignment Align_vector::get_alignment() {
return this->alignment;
}
bool Align_vector::check_range(int x, int y) {
switch(this->alignment) {
case Align_vector::left:
if(x == -1 && y == 0) {
return true;
}
else {
return false;
}
break;
case Align_vector::right:
if(x == 1 && y == 0) {
return true;
}
else {
return false;
}
break;
case Align_vector::down:
if(x == 0 && y == -1) {
return true;
}
else {
return false;
}
break;
case Align_vector::up:
if(x == 0 && y == 1) {
return true;
}
else {
return false;
}
break;
default:
return false;
break;
}
}
} /* namespace Utils */
Finally, you are missing a definition of a default constructor for base
class Vector_2d (your comments suggest that you did not define that class
at all, you just declared its existance using statement class Vector_2d;).
Judging by your overall implementation, I think user #molbdnilo was correct
when suggesting you should learn more about C++ programming in general.
Hope this helps!

Building a math expression evaluator

I can't use boost::spirit in my environment. But I would like to use STL and boost as much as possible to build my own expression evaluator. Is there such an alternative to boost::spirit?
The following code includes unit tests and a complete parser I wrote in an about 90 minute session at ACCU 200x (8 or 9). If you need more, it might be easy enough to extend. You can make it do doubles by defining Parse::value_type, or extracting it into a separate header file and make it a template class.
Or you can take the test cases and try yourself. (it uses CUTE from http://cute-test.com)
#include "cute.h"
#include "ide_listener.h"
#include "cute_runner.h"
#include <cctype>
#include <map>
namespace {
class Parser {
typedef int value_type;
typedef std::vector<value_type> valuestack;
typedef std::vector<char> opstack;
typedef std::map<std::string,value_type> memory;
public:
memory variables;
private:
void evaluateSingleOperator(char op,value_type &result,value_type operand) {
switch(op) {
case '+': result += operand; break;
case '-': result -= operand; break;
case '*': result *= operand; break;
case '/': result /= operand; break;
default: throw("invalid operand");
}
}
void evaluateStacks(valuestack &values, opstack &ops) {
while(ops.size() && values.size()>1) {
char op = ops.back(); ops.pop_back();
value_type operand = values.back(); values.pop_back();
evaluateSingleOperator(op,values.back(),operand);
}
}
bool higherPrecedenceOrLeftAssociative(char last, char current) {
return (last == current)||(last == '*' || last == '/') ;
}
bool shouldEvaluate(char op,opstack const &ops) {
return ops.size() > 0 && higherPrecedenceOrLeftAssociative(ops.back(),op);
}
std::string parseVariableName(std::istream &is) {
std::string variable;
char nextchar=0;
while ((is >> nextchar) && isalpha(nextchar)) {
variable += nextchar;
}
if (variable.size() == 0) throw std::string("internal parse error");
is.unget();
return variable;
}
int peekWithSkipWhiteSpace(std::istream &is) {
int nextchar = EOF;
while(isspace(nextchar = is.peek())) is.get();
return nextchar;
}
value_type getOperand(std::istream &is) {
int nextchar = peekWithSkipWhiteSpace(is);
if (nextchar == EOF) throw std::string("syntax error operand expected");
if (isdigit(nextchar)){
value_type operand=0;
if (!(is >> operand)) throw std::string("syntax error getting number") ;
return operand;
} else if ('(' == nextchar) {
is.get();
return parse(is);
} else if (isalpha(nextchar)) {
std::string variable= parseVariableName(is);
if( parseAssignmentOperator(is)) {
variables[variable] = parse(is);
} else {
if (!variables.count(variable)) throw std::string("undefined variable: ")+variable;
}
return variables[variable];
}
throw std::string("syntax error");
}
bool parseAssignmentOperator(std::istream &is) {
int nextchar = peekWithSkipWhiteSpace(is);
if ('=' != nextchar) {
return false;
}
is.get();
return true;
}
public:
value_type parse(std::istream &is) {
is >> std::skipws;
valuestack values;
opstack ops;
values.push_back(getOperand(is));
char op=')';
while((is >>op) && op != ')') {
if (shouldEvaluate(op, ops)) {
evaluateStacks(values, ops);
}
values.push_back(getOperand(is));
ops.push_back(op);
}
evaluateStacks(values,ops);
return values.back();
}
value_type eval(std::string s) {
std::istringstream is(s);
return parse(is);
}
};
int eval(std::string s) {
return Parser().eval(s);
}
void shouldThrowEmptyExpression() {
eval("");
}
void shouldThrowSyntaxError() {
eval("()");
}
void testSimpleNumber() {
ASSERT_EQUAL(5,eval("5"));
}
void testSimpleAdd() {
ASSERT_EQUAL(10,eval("5 +5"));
}
void testMultiAdd() {
ASSERT_EQUAL(10,eval("1 + 2 + 3+4"));
}
void testSimpleSubtract() {
ASSERT_EQUAL(5,eval("6-1"));
}
void testTenPlus12Minus100() {
ASSERT_EQUAL(-78,eval("10+12-100"));
}
void testMultiply() {
ASSERT_EQUAL(50,eval("10*5"));
}
void testDivision() {
ASSERT_EQUAL(7,eval("21/3"));
}
void testAddThenMultiply() {
ASSERT_EQUAL(21,eval("1+4 *5"));
}
void testAddThenMultiplyAdd() {
ASSERT_EQUAL(16,eval("1+4*5 -5"));
}
void testAddSubSub() {
ASSERT_EQUAL(-4,eval("1+2-3-4"));
}
void testSimpleParenthesis() {
ASSERT_EQUAL(1,eval("(1)"));
}
void testSimpleOperandParenthesis() {
ASSERT_EQUAL(2,eval("1+(1)"));
}
void testParenthesis() {
ASSERT_EQUAL(5,eval("2*(1+4)-5"));
}
void testNestedParenthesis() {
ASSERT_EQUAL(16,eval("2*(1+(4*3)-5)"));
}
void testDeeplyNestedParenthesis() {
ASSERT_EQUAL(8,eval("((2*((1+(4*3)-5)))/2)"));
}
void testSimpleAssignment() {
Parser p;
ASSERT_EQUAL(1, p.eval("a=1*(2-1)"));
ASSERT_EQUAL(8, p.eval("a+7"));
ASSERT_EQUAL(1, p.eval("2-a"));
}
void testLongerVariables() {
Parser p;
ASSERT_EQUAL(1, p.eval("aLongVariableName=1*(2-1)"));
ASSERT_EQUAL(42, p.eval("AnotherVariable=7*(4+2)"));
ASSERT_EQUAL(1, p.eval("2-(aLongVariableName*AnotherVariable)/42"));
}
void shouldThrowUndefined() {
eval("2 * undefinedVariable");
}
void runSuite(){
cute::suite s;
//TODO add your test here
s.push_back(CUTE_EXPECT(CUTE(shouldThrowEmptyExpression),std::string));
s.push_back(CUTE_EXPECT(CUTE(shouldThrowSyntaxError),std::string));
s.push_back(CUTE(testSimpleNumber));
s.push_back(CUTE(testSimpleAdd));
s.push_back(CUTE(testMultiAdd));
s.push_back(CUTE(testSimpleSubtract));
s.push_back(CUTE(testTenPlus12Minus100));
s.push_back(CUTE(testMultiply));
s.push_back(CUTE(testDivision));
s.push_back(CUTE(testAddThenMultiply));
s.push_back(CUTE(testAddSubSub));
s.push_back(CUTE(testAddThenMultiplyAdd));
s.push_back(CUTE(testSimpleParenthesis));
s.push_back(CUTE(testSimpleOperandParenthesis));
s.push_back(CUTE(testParenthesis));
s.push_back(CUTE(testNestedParenthesis));
s.push_back(CUTE(testDeeplyNestedParenthesis));
s.push_back(CUTE(testSimpleAssignment));
s.push_back(CUTE(testLongerVariables));
s.push_back(CUTE_EXPECT(CUTE(shouldThrowUndefined),std::string));
cute::ide_listener lis;
cute::makeRunner(lis)(s, "The Suite");
}
}
int main(){
runSuite();
}
YACC++ is a very good tool for parser generator for c++ applications. ANTLR is also agood option howver that does not have good documentation for its usage in C/C++.

How do I refactor code into a subroutine but allow for early exit?

There's a really obvious refactoring opportunity in this (working) code.
bool Translations::compatibleNICodes(const Rule& rule,
const std::vector<std::string>& nicodes)
{
bool included = false;
// Loop through the ni codes.
for(std::vector<std::string>::const_iterator iter = nicodes.begin();
iter != nicodes.end();
++iter)
{
// Match against the ni codes of the rule
if(rule.get_ni1() == *iter)
{
// If there's a match, check if it's flagged include or exclude
const std::string flag = rule.get_op1();
// If include, code is included unless a later rule excludes it
if(flag == "INCLUDE"){ included = true; }
// If exclude, code is specifically excluded
else if(flag == "EXCLUDE"){ return false; }
}
if(rule.get_ni2() == *iter)
{
const std::string flag = rule.get_op2();
if(flag == "INCLUDE"){ included = true; }
else if(flag == "EXCLUDE"){ return false; }
}
if(rule.get_ni3() == *iter)
{
const std::string flag = rule.get_op3();
if(flag == "INCLUDE"){ included = true; }
else if(flag == "EXCLUDE"){ return false; }
}
if(rule.get_ni4() == *iter)
{
const std::string flag = rule.get_op4();
if(flag == "INCLUDE"){ included = true; }
else if(flag == "EXCLUDE"){ return false; }
}
if(rule.get_ni5() == *iter)
{
const std::string flag = rule.get_op5();
if(flag == "INCLUDE"){ included = true; }
else if(flag == "EXCLUDE"){ return false; }
}
}
return included;
}
I want to turn it to something like:
bool Translations::compatibleNICodes(const Rule& rule,
const std::vector<std::string>& nicodes)
{
bool included = false;
// Loop through the ni codes.
for(std::vector<std::string>::const_iterator iter = nicodes.begin();
iter != nicodes.end();
++iter)
{
// Match against the ni codes of the rule
included |= matchNICode(rule.get_ni1(), rule.get_op1);
included |= matchNICode(rule.get_ni2(), rule.get_op2);
included |= matchNICode(rule.get_ni3(), rule.get_op3);
included |= matchNICode(rule.get_ni4(), rule.get_op4);
included |= matchNICode(rule.get_ni5(), rule.get_op5);
}
return included;
}
bool Translations::matchNICode(const std::string& ni,
const std::string& op)
{
if(ni == *iter)
{
if(op == "INCLUDE"){ return true; }
else if(op == "EXCLUDE"){ /*Return worse than false*/ }
}
return false;
}
The problem is that I can't get around the problem that I want to exit early if it's an exclude statement.
Note that I can't change the structure of the Rule class.
Any advice?
One possible refactoring is the below, but I'm not sure if it is worth the trouble
#define NI_CLAUSE(ID) \
if(rule.get_ni ## ID() == *iter) \
{ \
const std::string flag = rule.get_op ## ID(); \
if(flag == "INCLUDE"){ included = true; } \
else if(flag == "EXCLUDE"){ return false; } \
}
bool Translations::compatibleNICodes(const Rule& rule,
const std::vector<std::string>& nicodes)
{
bool included = false;
// Loop through the ni codes.
for(std::vector<std::string>::const_iterator iter = nicodes.begin();
iter != nicodes.end();
++iter)
{
NI_CLAUSE(1)
NI_CLAUSE(2)
NI_CLAUSE(3)
NI_CLAUSE(4)
NI_CLAUSE(5)
}
return included;
}
I assume that the get_niX() or get_opX() methods have some kind of side effect; otherwise, as soon as you get a true, you would want to exit.
If the thing returned from matchNICode() is really worse than false, it may be an exception. In this case, it is quite simple:
bool Translations::compatibleNICodes(const Rule& rule,
const std::vector<std::string>& nicodes)
{
bool included = false;
try
{
// Loop through the ni codes.
for(std::vector<std::string>::const_iterator iter = nicodes.begin();
iter != nicodes.end();
++iter)
{
// Match against the ni codes of the rule
included |= matchNICode(rule.get_ni1(), rule.get_op1);
included |= matchNICode(rule.get_ni2(), rule.get_op2);
included |= matchNICode(rule.get_ni3(), rule.get_op3);
included |= matchNICode(rule.get_ni4(), rule.get_op4);
included |= matchNICode(rule.get_ni5(), rule.get_op5);
}
return included;
}
catch (WorseThanFalseException& ex)
{
return false; // Or whatever you have to do and return
}
}
bool Translations::matchNICode(const std::string& ni,
const std::string& op)
{
if(ni == *iter)
{
if(op == "INCLUDE"){ return true; }
else if(op == "EXCLUDE"){ throw WorseThanFalseException(); } // Whatever this is
}
return false;
}
bool Translations::compatibleNICodes(const Rule& rule,
const std::vector<std::string>& nicodes)
{
bool included = false;
struct
{
RULE_GET_NI get_ni;
RULE_GET_OP get_op;
} method_tbl[] =
{
{ &Rule::get_ni1, &Rule::get_op1 },
{ &Rule::get_ni2, &Rule::get_op2 },
{ &Rule::get_ni3, &Rule::get_op3 },
{ &Rule::get_ni4, &Rule::get_op4 },
{ &Rule::get_ni5, &Rule::get_op5 },
};
// Loop through the ni codes.
for(std::vector<std::string>::const_iterator iter = nicodes.begin();
iter != nicodes.end();
++iter)
{
for(size_t n = 0; n < 5 /* I am lazy here */; ++n)
{
if((rule.*(method_tbl[n].get_ni))() == *iter)
{
// If there's a match, check if the rule is include or exclude
const std::string flag = (rule.*(method_tbl[n].get_op))();
// If include, code is included unless a later rule excludes it
if(flag == "INCLUDE"){ included = true; }
// If exclude, code is specifically excluded
else if(flag == "EXCLUDE"){ return false; }
}
}
}
return included;
}
The answer was edited to include only final version.
BTW this problem is fun, just give me some more time and I come up with stl algorithm and functor...
Obviously the code would be much cleaner and simpler, if you could iterate through the ni and op members of Rule in a loop. If you can't refactor Rule, maybe you could create a wrapper around it to achieve this goal.
If you have a single method with such code, I wouldn't bother though. IMO this would only pay off if you can eliminate the duplicated code in several similar methods.
You could get around by creating some kind of tribool class and use lazy evaluation.
class TriState
{
public:
TriState(): mState(KO) {}
bool isValid() const { return mState != FATAL; }
bool ok() const { return mState == OK; }
void update(std::string const& value,
std::string const& reference,
std::string const& action)
{
if (mState == FATAL) return;
if (value == reference)
{
if (action == "INCLUDE") mState = OK;
else if (action == "EXCLUDE") mState = FATAL;
}
}
private:
typedef enum { OK, KO, FATAL } State_t;
State_t mState;
};
Then you can use the loop as such:
TriState state;
for (const_iterator it = nicodes.begin(), end = nicodes.end();
it != end && state.isValid(); ++it)
{
state.update(*it, rule.get_ni1(), rule.get_op1);
state.update(*it, rule.get_ni2(), rule.get_op2);
state.update(*it, rule.get_ni3(), rule.get_op3);
state.update(*it, rule.get_ni4(), rule.get_op4);
state.update(*it, rule.get_ni5(), rule.get_op5);
}
return state.ok();
Now, if the operation on rule have some kind of side effect that should be avoided, you use a wrapper to get lazy evaluation.
class Wrapper
{
public:
Wrapper(Rule const& rule): mRule(rule) {}
std::string const& get_ni(size_t i) const { switch(i) { ... } }
std::string const& get_action(size_t i) const { switch(i) { ... } }
private:
Rule const& mRule;
};
Refactor update:
void update(std::string const& value, Wrapper wrapper, size_t index)
{
if (mState == FATAL) return;
if (value == wrapper.get_ni(index))
{
if (wrapper.get_action(index) == "INCLUDE") mState = OK;
else if (wrapper.get_action(index) == "EXCLUDE") mState = FATAL;
}
}
Use a double loop:
TriState state;
Wrapper wrapper(rule);
for (const_iterator it = nicodes.begin(), end = nicodes.end();
it != end && state.isValid(); ++it)
{
for (size_t index = 1; index != 6 && state.isValid(); ++index)
state.update(*it, wrapper, index);
}
return state.ok();
Guideline: Wrap what you can't modify! (look at the Adaptor family of Patterns)
This is promised algorithms based solution and it is hardcore... And they are saying STL is here to simplify our programs (this is waaaay longer then the other solution I proposed).
struct FUNCTOR : std::unary_function<bool, std::string const &>
{
public:
FUNCTOR(Rule const &r) : included(false), rule(r)
{
}
// returns true if exluded
bool operator()(std::string const &s)
{
static METHODS methods[] =
{
{ &Rule::get_ni1, &Rule::get_op1 },
{ &Rule::get_ni2, &Rule::get_op2 },
{ &Rule::get_ni3, &Rule::get_op3 },
{ &Rule::get_ni4, &Rule::get_op4 },
{ &Rule::get_ni5, &Rule::get_op5 },
};
return(std::find_if(&methods[0], &methods[5], FUNCTOR2(rule, s, included)) != &methods[5]);
}
operator bool()
{
return(included);
}
private:
struct METHODS
{
std::string (Rule::*get_ni)() const;
std::string (Rule::*get_op)() const;
};
struct FUNCTOR2 : std::unary_function<bool, METHODS>
{
public:
FUNCTOR2(Rule const &r, std::string const &s, bool &incl) : rule(r), str(s), included(incl)
{
}
// return true if exluded
bool operator()(METHODS m)
{
if((rule.*m.get_ni)() == str)
{
// If there's a match, check if the rule is include or exclude
const std::string flag = (rule.*m.get_op)();
// If include, code is included unless a later rule excludes it
if(flag == "INCLUDE")
included = true;
// If exclude, code is specifically excluded
else if(flag == "EXCLUDE")
{
included = false;
return(true);
}
}
return(false);
}
private:
Rule const &rule;
std::string const &str;
bool &included;
};
Rule const &rule;
bool included;
};
bool Translations::compatibleNICodes(const Rule& rule,
const std::vector<std::string>& nicodes)
{
FUNCTOR f(rule);
std::find_if(nicodes.begin(), nicodes.end(), f);
return(f);
}
Using a in/out parameter is a straightforward and efficient way to get two return values:
Plus I presume you need lazy evaluation of rule.get_opN()? To do that you'll need to use a pointer-to-member-function.
bool Translations::compatibleNICodes(const Rule& rule,
const std::vector<std::string>& nicodes)
{
bool included = false;
// Loop through the ni codes.
for(std::vector<std::string>::const_iterator iter = nicodes.begin();
iter != nicodes.end();
++iter)
{
// Match against the ni codes of the rule
if (!matchNICode(rule.get_ni1(), rule, &Rule::get_op1, included)) return false;
if (!matchNICode(rule.get_ni2(), rule, &Rule::get_op2, included)) return false;
if (!matchNICode(rule.get_ni3(), rule, &Rule::get_op3, included)) return false;
if (!matchNICode(rule.get_ni4(), rule, &Rule::get_op4, included)) return false;
if (!matchNICode(rule.get_ni5(), rule, &Rule::get_op5, included)) return false;
}
return included;
}
bool Translations::matchNICode(const std::string& ni, const Rule& rule,
std::string (Rule::* const opfn)(), bool& included)
{
if (ni == *iter) {
const std::string& op = (rule.*opfn)();
if (op == "INCLUDE") {
included = true;
}
else if (op == "EXCLUDE") {
return false;
}
}
return true;
}