Tri-Edge AI
Tri-Edge AI

Reputation: 340

Macro metaprogramming horror

I am trying to do something like:

custommacro x;

which would expand into:

declareSomething; int x; declareOtherthing;

Is this even possible?

I already tricked it once with operator= to behave like that, but it can't be done with declarations.

Upvotes: 0

Views: 1896

Answers (3)

VITTUIX-MAN
VITTUIX-MAN

Reputation: 376

You could use for-loop and GnuC statement expression extension.

#define MY_MACRO\
  FOR_MACRO(_uniq##__COUNTER__##name,{/*declareSomething*/ },{ /* declareOtherthing */ }) int

#define FOR_MACRO(NAME,FST_BLOCK,SND_BLOCK)\
  for(int NAME = ({FST_BLOCK ;0;}); NAME<1 ; NAME++,(SND_BLOCK))

It's "practically hygienic", though this means that whatever you do inside those code blocks wont escape the for-loop scope.

Upvotes: 1

Alex Celeste
Alex Celeste

Reputation: 13370

You can elide the parentheses as long as you are willing to accept two additions:

  1. the whole code needs to be wrapped in a block macro
  2. there needs to be something following the echo directive

e.g. thusly:

#define LPAREN (
#define echo ECHO_MACRO LPAREN
#define done )

#define ECHO_MACRO(X) std::cout << (X) << "\n"

#define DSL(X) X

...
DSL(
    echo "Look ma, no brains!" done;
)
...

Reasons for this:

  1. There is no way to make a function-like macro expand without parentheses. This is just a basic requirement of the macro language; if you want something else investigate a different macro processor
  2. Therefore, we need to insert the parentheses; in turn we need to have something after the directive, like a done macro, that will expand to a form containinf the necessary close paren
  3. Unfortunately, because the echo ... done form didn't look like a macro invocation to the preprocessor, it wasn't marked for expansion when the preprocessor entered it, and whether we put parens in or not is irrelevant. Just using echo ... done will therefore dump an ECHO_MACRO call in the text
  4. Text is re-scanned, marked for expansion, and expanded again when it is the argument to a function-like macro, so wrapping the entire block with a block macro (here it's DSL) will cause the call to ECHO_MACRO to be expanded on this rescan pass (DSL doesn't do anything with the result: it exists just to force the rescan)
  5. We need to hide the ( in the expansion of echo behind the simple macro LPAREN, because otherwise the unmatched parenthesis in the macro body will confuse the preprocessor

If you wanted to create an entire domain-specific language for such commands, you could also reduce the number of done commands by making the core commands even more unwieldy:

#define LPAREN (

#define begin NO_OP LPAREN 0
#define done );

#define echo ); ECHO_MACRO LPAREN
#define write ); WRITE_MACRO LPAREN
#define add ); ADD_MACRO LPAREN
#define sub ); SUB_MACRO LPAREN

#define NO_OP(X) 
#define ECHO_MACRO(X) std::cout << (X) << "\n"
#define WRITE_MACRO(X) std::cout << (X)
#define ADD_MACRO(D, L, R) (D) = (L) + (R)
#define SUB_MACRO(D, L, R) (D) = (L) - (R)

#define DSL(X) DSL_2 X
#define DSL_2(X) X

int main(void) {
int a, b;
DSL((
    begin
      add a, 42, 47
      sub b, 64, 50
      write "a is:  "
      echo a
      write "b is:  "
      echo b
    done
))
return 0;
}

In this form, each command is pre-designed to close the preceding command, so that only the last one needs a done; you need a begin line so that there's an open command for the first real operation to close, otherwise the parens will mismatch.

Messing about like this is much easier in C than in C++, as C's preprocessor is more powerful (it supports __VA_ARGS__ which are pretty much essential for complicated macro metaprogramming).

Oh yeah, and one other thing -

...please never do this in real code.

Upvotes: 6

David G
David G

Reputation: 96790

I understand what you're trying to do and it simply can't be done. A macro is only text replacement, it has no knowledge of what comes after it, so trying to do custommacro x will expand to whatever custommacro is, a space, and then x, which just won't work semantically.

Also, about your echo hack: this is actually very simple with the use of operators in C++:

#include <iostream>

#define echo std::cout <<

int main()
{
    echo "Hello World!";
}

But you really shouldn't be writing code like this (that is, using macros and a psuedo-echo hack). You should write code that conforms to the syntax of the language and the semantics of what you're trying to do. If you want to write to standard output use std::cout. Moreover, if you want to use echo, make a function called echo that invokes std::cout internally, but don't hack the features of the language to create your own.

Upvotes: 2

Related Questions