8

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

6
  • No, you can't do it. Specially not the way to replace {} by []. Commented Jun 1, 2017 at 9:11
  • The result part was not clear sorry, see my edit Commented Jun 1, 2017 at 9:21
  • Is there a limit to the amount of arguments, or should it support an arbitrary amount? Commented Jun 1, 2017 at 9:24
  • No limits. Something like MAGIC_MACRO(...) Commented Jun 1, 2017 at 9:26
  • This is probably going to depend on what the "more complicated macro" currently looks like. Commented Jun 1, 2017 at 18:07

4 Answers 4

6

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__))
Sign up to request clarification or add additional context in comments.

Comments

3

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

5 Comments

Thanks but I'm not planning to use boost
@oneiros: You can use the preprocessor library without having to drag in the rest of Boost. ("The Boost Preprocessing library is a library of macros, with support for preprocessor metaprogramming. The library supports both C++ and C compilation. It does not depend on any other Boost libraries and therefore may be used as a standalone library.") So you can just download it and put it into your include directory. There is no binary file; the library consists only of macro definitions.
@Oneiros: Why not?
I was looking for a standard no-dependencies solution
@Oneiros: Your own code is precisely as much of a dependency.
2

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.

3 Comments

Please don't put multiple statements on one line unless you have to. It severely impacts readability. As for a modernising suggestion: <array>
Actually, if dumpStrings returned a string instead, it would become 1 statement on the line
Yeah. Edited a bit.
0

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)
};

1 Comment

This is what I am used to as well. Is there a way to replace this with something like templates?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.