Oneiros
Oneiros

Reputation: 4378

C++ Macro to create a string array

Is there a way to create a std::string (or char*) array with a pre-processor macro?

Something like this:

std::string myStrings[] = {MAGIC_MACRO(a, b, c)};

Result:

std::string myStrings[] = {"a", "b", "c"}

I know it looks pointless but I need it in a more complicated macro that has a variable number of arguments

Upvotes: 8

Views: 7793

Answers (4)

Andre Kampling
Andre Kampling

Reputation: 5631

The code below is working for what you've asked for with up to 1024 arguments and without using additional stuff like boost. It defines an EVAL(...) and also a MAP(m, first, ...) macro to do recursion and to use for each iteration the macro m with the next parameter first.

With the use of that, your MAGIC_MACRO(...) looks like: #define MAGIC_MACRO(...) EVAL(MAP(STRINGIZE, __VA_ARGS__)).

It is mostly copied from C Pre-Processor Magic. It is also great explained there. You can also download these helper macros like EVAL(...) at this git repository, there are also a lot of explanation in the actual code. It is variadic so it takes the number of arguments you want.

But I changed the FIRST and the SECOND macro as it uses a Gnu extension like it is in the source I've copied it from.

Main function part:

int main()
{
   std::string myStrings[] = { MAGIC_MACRO(a, b, c) }; // Expands to: std::string myStrings[] = { "a" , "b" , "c" };
   std::string myStrings[] = { MAGIC_MACRO(a, b, c, x, y, z) }; // Expands to: std::string myStrings[] = { "a" , "b" , "c", "x" , "y" , "z" };
}

Macro definitions:

#define FIRST_(a, ...) a
#define SECOND_(a, b, ...) b

#define FIRST(...) FIRST_(__VA_ARGS__,)
#define SECOND(...) SECOND_(__VA_ARGS__,)

#define EMPTY()

#define EVAL(...) EVAL1024(__VA_ARGS__)
#define EVAL1024(...) EVAL512(EVAL512(__VA_ARGS__))
#define EVAL512(...) EVAL256(EVAL256(__VA_ARGS__))
#define EVAL256(...) EVAL128(EVAL128(__VA_ARGS__))
#define EVAL128(...) EVAL64(EVAL64(__VA_ARGS__))
#define EVAL64(...) EVAL32(EVAL32(__VA_ARGS__))
#define EVAL32(...) EVAL16(EVAL16(__VA_ARGS__))
#define EVAL16(...) EVAL8(EVAL8(__VA_ARGS__))
#define EVAL8(...) EVAL4(EVAL4(__VA_ARGS__))
#define EVAL4(...) EVAL2(EVAL2(__VA_ARGS__))
#define EVAL2(...) EVAL1(EVAL1(__VA_ARGS__))
#define EVAL1(...) __VA_ARGS__

#define DEFER1(m) m EMPTY()
#define DEFER2(m) m EMPTY EMPTY()()

#define IS_PROBE(...) SECOND(__VA_ARGS__, 0)
#define PROBE() ~, 1

#define CAT(a,b) a ## b

#define NOT(x) IS_PROBE(CAT(_NOT_, x))
#define _NOT_0 PROBE()

#define BOOL(x) NOT(NOT(x))

#define IF_ELSE(condition) _IF_ELSE(BOOL(condition))
#define _IF_ELSE(condition) CAT(_IF_, condition)

#define _IF_1(...) __VA_ARGS__ _IF_1_ELSE
#define _IF_0(...)             _IF_0_ELSE

#define _IF_1_ELSE(...)
#define _IF_0_ELSE(...) __VA_ARGS__

#define COMMA ,

#define HAS_ARGS(...) BOOL(FIRST(_END_OF_ARGUMENTS_ __VA_ARGS__)())
#define _END_OF_ARGUMENTS_() 0

#define MAP(m, first, ...)           \
  m(first)                           \
  IF_ELSE(HAS_ARGS(__VA_ARGS__))(    \
    COMMA DEFER2(_MAP)()(m, __VA_ARGS__)   \
  )(                                 \
    /* Do nothing, just terminate */ \
  )
#define _MAP() MAP

#define STRINGIZE(x) #x
#define MAGIC_MACRO(...) EVAL(MAP(STRINGIZE, __VA_ARGS__))

Upvotes: 6

hyde
hyde

Reputation: 62797

Simple solution is to have a separate macro for each different count. Using 2-level "stringify" macro pattern (read more about it here) you can do something like this:

#include <iostream>
#include <sstream>

#define XSTRINGIFY(s) #s

#define STRINGARRAY1(s0) { XSTRINGIFY(s0) }
#define STRINGARRAY2(s0, s1) { XSTRINGIFY(s0), XSTRINGIFY(s1) }
#define STRINGARRAY3(s0, s1, s2) { XSTRINGIFY(s0), XSTRINGIFY(s1), XSTRINGIFY(s2) }

using namespace std;

string dumpStrings(string *array, int count) {
    stringstream ss;
    if (count > 0) {
        ss << '"' << array[0] << '"';
        for(int i = 1; i < count; ++i) {
            ss << ", \"" << array[i]<< '"';
        }
    }
    return ss.str();
}

int main()
{
    string strings1[1] = STRINGARRAY1(a);
    string strings2[2] = STRINGARRAY2(a, b);
    string strings3[3] = STRINGARRAY3(a, b, c);
    cout << "strings1: " << dumpStrings(strings1, sizeof(strings1) / sizeof(strings1[0])) << endl;
    cout << "strings2: " << dumpStrings(strings2, sizeof(strings2) / sizeof(strings2[0])) << endl;
    cout << "strings3: " << dumpStrings(strings3, sizeof(strings3) / sizeof(strings3[0])) << endl;
}

Output:

strings1: "a"
strings2: "a", "b"
strings3: "a", "b", "c"

If you want just one macro which takes variable number of arguments, it gets a bit messy, as shown in the other answers.

Upvotes: 2

Paul Floyd
Paul Floyd

Reputation: 6906

I'm not sure if this corresponds to what you're trying to achieve, but I generally use this technique to generate strings (or other lists generated from the strings like enum elements).

E.g.,

#define FOREACH_APPLY(GENERATE) \
        GENERATE(a)   \
        GENERATE(b)  \
        GENERATE(c)

#define GENERATE_STRING(STRING) #STRING,

std::string myStrings[] = {
    FOREACH_APPLY(GENERATE_STRING)
};

Upvotes: 0

Quentin
Quentin

Reputation: 63124

Maybe there's a more efficient way, but you can simply use Boost.PP:

#define MAGIC_MACRO_ELEM(r, data, i, elem) \
    BOOST_PP_COMMA_IF(i) BOOST_PP_STRINGIZE(elem)

#define MAGIC_MACRO(...) \
    BOOST_PP_SEQ_FOR_EACH_I(MAGIC_MACRO_ELEM, ~, BOOST_PP_TUPLE_TO_SEQ((__VA_ARGS__)))

See it live on Coliru

Upvotes: 3

Related Questions