Questor
Questor

Reputation: 173

Macro to generate chained else ifs

Using an X Macro to keep a couple of different things in sync using an out of module provided list.

Part of this involves creating an else/if chain to validate a string.

I am currently doing this:

if (0) {
   // this will never run, and be compiled out
}
#define X(name) else if (some_thing == #name) { // do thing based on name... }
   MY_LIST
#undef X
else {
   // Report unrecognized string...
}

but it feels a little bit ugly to me (I am not a big fan of the un-executable if(0) {}).

I have thought about doing this using a switch statement...

constexpr hash(const char* const str, size_t len, uint64_t init_value)
   // implementation left to imagination of the reader)...

//...

switch(hash(some_thing, len(some_thing))) {
#define X(name) case hash(#name, const_len(#name)): { break; }
   MY_LIST
#undef X
   default: 
   {
      good_thing = false;
      break;
   }
}
// Now that it has been validated as a good string... save it etc.

But I worry about collisions (as unlikely as a collision is).

Is there a way to do the if/else chain without starting with an if(0)?

I am stuck with std::14 if it helps (I know it doesn't).

Upvotes: 2

Views: 111

Answers (4)

Lundin
Lundin

Reputation: 214770

It's actually pretty easy given that there is no such thing as else if in C or C++. There is just else which might refer to an immediately following if statement.

Solution:

#define IF_ELSE(n) if(n==3) { puts("bingo!"); } else

LIST(IF_ELSE); // note the semicolon here when calling the X-macro list

Full example:

#include <stdio.h>

#define LIST(X) \
  X(1)          \
  X(2)          \
  X(3)          \

int main() 
{
    #define IF_ELSE(n) if(n==3) { puts("bingo!"); } else
    LIST(IF_ELSE); // note the semicolon here

    return 0;
}

Preprocessor output:

if(1==3) { puts("bingo!"); } else if(2==3) { puts("bingo!"); } else if(3==3) { puts("bingo!"); } else;

Indented as the language grammar always treats "else if":

if(1==3) 
{ 
  puts("bingo!"); 
} 
else 
  if(2==3) 
  { 
    puts("bingo!"); 
  } 
  else 
    if(3==3) 
    { 
      puts("bingo!"); 
    } 
    else
      ;

Upvotes: 0

Botje
Botje

Reputation: 31020

What is an if-else chain but a series of if statements that terminate as soon as one works? In this case we can use break together with do-while:

do {
#  define X(name) if (some_thing == #name) { // do thing based on name...; break; }
   MY_LIST
#  undef X

// Unrecognized case goes here.

} while (0);

Upvotes: 1

Eugene
Eugene

Reputation: 7688

You can set a flag in each string match - then you don't need else:

bool found = false;
#define X(name) if (some_thing == #name) { found = true; /* do thing based on name here ...*/ }
   MY_LIST
#undef X
if (!found) {
   // Report unrecognized string...
}

Or, if it is important to avoid comparisons after a string is matched, you can do it like this (but it is harder to understand in my opinion):

#define X(name) if (some_thing == #name) { /* do thing based on name... */ } else 
   MY_LIST
#undef X
{
   // Report unrecognized string...
}

Upvotes: 2

GandhiGandhi
GandhiGandhi

Reputation: 1350

Is there a way to do the if/else chain without starting with an if(0)?

I don't know of a better way to generate an if-else chain with macros. I agree it does look a little hacky.

However, here's another way way to validate that this string is one in the set built from X-Macros that compiles for C++14. Up to your taste if you think its better or not for your codebase.

#include <set>
#include <string>

void validate_string(std::string some_thing) {
    /* Build the set of strings to test against with X-Macros */
    static const std::set<std::string> validate_set = {
    #define X(name) #name,
    MY_LIST
    #undef X
    };

    if (validate_set.find(some_thing) != validate_set.end()){
        /* Then some_thing is one of the compile time constants */
    }
    
}

Upvotes: 2

Related Questions