Asked  6 Months ago    Answers:  5   Viewed   99 times

I have two macros FOO2 and FOO3:

#define FOO2(x,y) ...
#define FOO3(x,y,z) ...

I want to define a new macro FOO as follows:

#define FOO(x,y) FOO2(x,y)
#define FOO(x,y,z) FOO3(x,y,z)

But this doesn't work because macros do not overload on number of arguments.

Without modifying FOO2 and FOO3, is there some way to define a macro FOO (using __VA_ARGS__ or otherwise) to get the same effect of dispatching FOO(x,y) to FOO2, and FOO(x,y,z) to FOO3?

 Answers

17

Simple as:

#define GET_MACRO(_1,_2,_3,NAME,...) NAME
#define FOO(...) GET_MACRO(__VA_ARGS__, FOO3, FOO2)(__VA_ARGS__)

So if you have these macros:

FOO(World, !)         # expands to FOO2(World, !)
FOO(foo,bar,baz)      # expands to FOO3(foo,bar,baz)

If you want a fourth one:

#define GET_MACRO(_1,_2,_3,_4,NAME,...) NAME
#define FOO(...) GET_MACRO(__VA_ARGS__, FOO4, FOO3, FOO2)(__VA_ARGS__)

FOO(a,b,c,d)          # expeands to FOO4(a,b,c,d)

Naturally, if you define FOO2, FOO3 and FOO4, the output will be replaced by those of the defined macros.

Tuesday, June 1, 2021
 
Freddie
answered 6 Months ago
71
gcc -E file.c

or

g++ -E file.cpp

will do this for you. The -E switch forces the compiler to stop after the preprocessing phase, spitting all it’s got at the moment to standard output.

Note: Surely you must have some #include directives. The included files get preprocessed, too, so you might get lots of output.

For Visual C++ the switch is /E which spits the preprocessor output to screen.

Wednesday, June 2, 2021
 
JustSteveKing
answered 6 Months ago
63

Another possibility, which does not use sizeof nor a GCC extension is to add the following to your code

#define PP_COMMASEQ_N()                                    
          1,  1,  1,  1,                                   
          1,  1,  1,  1,  1,  1,  1,  1,  1,  1,           
          1,  1,  1,  1,  1,  1,  1,  1,  1,  1,           
          1,  1,  1,  1,  1,  1,  1,  1,  1,  1,           
          1,  1,  1,  1,  1,  1,  1,  1,  1,  1,           
          1,  1,  1,  1,  1,  1,  1,  1,  1,  1,           
          1,  1,  1,  1,  1,  1,  1,  1,  0,  0

#define PP_COMMA(...)    ,

#define PP_HASCOMMA(...)                                   
          PP_NARG_(__VA_ARGS__, PP_COMMASEQ_N())

#define PP_NARG(...)                                       
          PP_NARG_HELPER1(                                 
              PP_HASCOMMA(__VA_ARGS__),                    
              PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()),        
              PP_NARG_(__VA_ARGS__, PP_RSEQ_N()))

#define PP_NARG_HELPER1(a, b, N)    PP_NARG_HELPER2(a, b, N)
#define PP_NARG_HELPER2(a, b, N)    PP_NARG_HELPER3_ ## a ## b(N)
#define PP_NARG_HELPER3_01(N)    0
#define PP_NARG_HELPER3_00(N)    1
#define PP_NARG_HELPER3_11(N)    N

The result is

PP_NARG()       // expands to 0
PP_NARG(x)      // expands to 1
PP_NARG(x, 2)   // expands to 2

Explanation:

The trick in these macros is that PP_HASCOMMA(...) expands to 0 when called with zero or one argument and to 1 when called with at least two arguments. To distinguish between these two cases, I used PP_COMMA __VA_ARGS__ (), which returns a comma when __VA_ARGS__ is empty and returns nothing when __VA_ARGS__ is non-empty.

Now there are three possible cases:

  1. __VA_ARGS__ is empty: PP_HASCOMMA(__VA_ARGS__) returns 0 and PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()) returns 1.

  2. __VA_ARGS__ contains one argument: PP_HASCOMMA(__VA_ARGS__) returns 0 and PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()) returns 0.

  3. __VA_ARGS__ contains two or more arguments: PP_HASCOMMA(__VA_ARGS__) returns 1 and PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()) returns 1.

The PP_NARG_HELPERx macros are just needed to resolve these cases.

Edit:

In order to fix the func(0, ) problem, we need to test whether we have supplied zero or more arguments. The PP_ISZERO macro comes into play here.

#define PP_ISZERO(x)    PP_HASCOMMA(PP_ISZERO_HELPER_ ## x)
#define PP_ISZERO_HELPER_0    ,

Now let's define another macro which prepends the number of arguments to an argument list:

#define PP_PREPEND_NARG(...)                               
          PP_PREPEND_NARG_HELPER1(PP_NARG(__VA_ARGS__), __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER1(N, ...)                    
          PP_PREPEND_NARG_HELPER2(PP_ISZERO(N), N, __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER2(z, N, ...)                 
          PP_PREPEND_NARG_HELPER3(z, N, __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER3(z, N, ...)                 
          PP_PREPEND_NARG_HELPER4_ ## z (N, __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER4_1(N, ...)  0
#define PP_PREPEND_NARG_HELPER4_0(N, ...)  N, __VA_ARGS__

The many helpers are again needed to expand the macros to numeric values. Finally test it:

#define my_func(...)  func(PP_PREPEND_NARG(__VA_ARGS__))

my_func()          // expands to func(0)
my_func(x)         // expands to func(1, x)
my_func(x, y)      // expands to func(2, x, y)
my_func(x, y, z)   // expands to func(3, x, y, z)

Online example:

http://coliru.stacked-crooked.com/a/73b4b6d75d45a1c8

See also:

Please have also a look at the P99 project, which has much more advanced preprocessor solutions, like these.

Friday, June 18, 2021
 
Kevin_Kinsey
answered 6 Months ago
92

From § 16.2-4 ("Source file inclusion") of C++ 2003 draft:

A preprocessing directive of the form

# include pp-tokens new-line 

(that does not match one of the two previous forms) is permitted. The preprocessing tokens after include in the directive are processed just as in normal text (each identifier currently defined as a macro name is replaced by its replacement list of preprocessing tokens).

§ 6.10.2-4 of C99 says the same.

The "two previous forms" mentioned above are # include <h-char-sequence> and # include "q-char-sequence". The section seems too simple to summarize.

For other directives, macro expansion isn't performed on any identifier preprocessing token (note this behavior is not defined by the grammar, but by C++ § 16 / C § 6.10):

# if constant-expression new-line [group] 
# ifdef identifier new-line [group] 
# ifndef identifier new-line [group] 
# elif constant-expression new-line [group] 
# else new-line [group] 
# endif new-line 
# include pp-tokens new-line 
# define identifier replacement-list new-line 
# define identifier lparen [identifier-list] ) replacement-list new-line 
# undef identifier new-line 
# line pp-tokens new-line 
# error [pp-tokens] new-line 
# pragma [pp-tokens] new-line 
# new-line 

#line is explicitly macro-expanded by C++ § 16.4-5 / C § 6.10.4-5. Expansion for #error (C++ § 16.5 / C § 6.10.5) and #pragma (C++ § 16.6 / C § 6.10.6) isn't mentioned. C++ § 16.3-7 / C 6.10.3-8 states:

If a # preprocessing token, followed by an identifier, occurs lexically at the point at which a preprocessing directive could begin, the identifier is not subject to macro replacement.

C++ § 16.3.1 / C § 6.10.3.1-1 tells us that when the arguments to a macro function are substituted into the replacement-list, they are first macro expanded. Similarly, C++ § 16.3.4 / C § 6.10.3.4 has the preprocessor macro-expand the replacement-list after substitution.

In summary, macro expansion is done for #if, #elif, #include, #line, the arguments to a macro function and the body of a macro function when substituted. I think that's everything.

Thursday, August 26, 2021
 
Timur Mustafaev
answered 3 Months ago
92

Suppose you have a class like this:

class Element {
public:
    Element(int value) : value(value) {}
    int getValue() const { return value; }
private:
    int value;
};

There are four ways to define a binary operator such as +.

  1. As a free function with access to only the public members of the class:

    // Left operand is 'a'; right is 'b'.
    Element operator+(const Element& a, const Element& b) {
        return Element(a.getValue() + b.getValue());
    }
    

    e1 + e2 == operator+(e1, e2)

  2. As a member function, with access to all members of the class:

    class Element {
    public:
        // Left operand is 'this'; right is 'other'.
        Element operator+(const Element& other) const {
            return Element(value + other.value);
        }
        // ...
    };
    

    e1 + e2 == e1.operator+(e2)

  3. As a friend function, with access to all members of the class:

    class Element {
    public:
        // Left operand is 'a'; right is 'b'.
        friend Element operator+(const Element& a, const Element& b) {
            return a.value + b.value;
        }
        // ...
    };
    

    e1 + e2 == operator+(e1, e2)

  4. As a friend function defined outside the class body—identical in behaviour to #3:

    class Element {
    public:
        friend Element operator+(const Element&, const Element&);
        // ...
    };
    
    Element operator+(const Element& a, const Element& b) {
        return a.value + b.value;
    }
    

    e1 + e2 == operator+(e1, e2)

Saturday, September 4, 2021
 
shx2
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share