Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 352
Oneiros Avatar asked Jun 01 '17 09:06

Oneiros


People also ask

How can we make array of strings in C?

Here is how an array of C string can be initialized: #define NUMBER_OF_STRING 4 #define MAX_STRING_SIZE 40 char arr[NUMBER_OF_STRING][MAX_STRING_SIZE] = { "array of c string", "is fun to use", "make sure to properly", "tell the array size" };

How do you declare an array of strings?

A String Array is an Array of a fixed number of String values. A String is a sequence of characters. Generally, a string is an immutable object, which means the value of the string can not be changed. The String Array works similarly to other data types of Array.

Can I declare a variable in a macro in C?

In the C Programming Language, the #define directive allows the definition of macros within your source code. These macro definitions allow constant values to be declared for use throughout your code. Macro definitions are not variables and cannot be changed by your program code like variables.


3 Answers

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__))
like image 158
Andre Kampling Avatar answered Sep 17 '22 19:09

Andre Kampling


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

like image 27
Quentin Avatar answered Sep 17 '22 19:09

Quentin


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.

like image 30
hyde Avatar answered Sep 17 '22 19:09

hyde