10

I just discovered that you cannot always use brace initialization when passing arguments to macros. I found this when an ASSERT() macro failed to compile. However, the following example illustrates the problem:

#include <iostream>
#include <string>
using namespace std;

#define PRINT_SIZE( f ) cout << "Size=" << (f).size() << endl;

int main()
{
  PRINT_SIZE( string("ABC") );  // OK, prints: "Size=3"
  PRINT_SIZE( string{"ABC"} );  // OK, prints: "Size=3"

  PRINT_SIZE( string("ABCDEF",3) ); // OK, prints: "Size=3"
  PRINT_SIZE( string{"ABCDEF",3} ); // Error: macro 'PRINT_SIZE' passed 2 arguments, but takes just 1

   return 0;
}

Is there a reason why macros cannot be made to work with brace initialization?

Edit:

I have since discovered that you can also use a variadic macro, and that solves the problem perfectly:

#include <iostream>
#include <string>
using namespace std;

#define PRINT_SIZE( ... ) cout << "Size=" << (__VA_ARGS__).size() << endl;

int main()
{
  PRINT_SIZE( string("ABC") );  // OK, prints: "Size=3"
  PRINT_SIZE( string{"ABC"} );  // OK, prints: "Size=3"

  PRINT_SIZE( string("ABCDEF",3) ); // OK, prints: "Size=3"
  PRINT_SIZE( string{"ABCDEF",3} ); // OK, prints: "Size=3"

  return 0;
}
Barnett
  • 1,491
  • 12
  • 18
  • 2
    Macro language is a pretty primitive language, unconnected with most of the C++ grammar. – chris Apr 11 '15 at 14:21
  • 4
    Commas separate macro arguments. – Jonathan Leffler Apr 11 '15 at 14:22
  • 2
    To complement the answers, *The sequence of preprocessing tokens bounded by the outside-most matching parentheses forms the list of arguments for the function-like macro.* ***The individual arguments within the list are separated by comma preprocessing tokens, but comma preprocessing tokens between matching inner parentheses do not separate arguments.*** *If there are sequences of preprocessing tokens within the list of arguments that would otherwise act as preprocessing directives, the behavior is undefined.* – chris Apr 11 '15 at 14:24
  • @JonathanLeffler: Nope, they don't. Commas in inner parentheses intentionally do not separate arguments. It would be very easy to make the C++ preprocessor support inner braces as well. – Johannes Overmann May 16 '22 at 10:46

3 Answers3

17

The list is split into several macro parameters. When you write

PRINT_SIZE( string{"ABCDEF",3} );

This attempts to expand the macro PRINT_SIZE with two parameters, one string{"ABCDEF" and one 3}, which fails. This can be worked around in many cases (including yours) by adding another pair of parentheses:

PRINT_SIZE( (string{"ABCDEF",3}) );

These parentheses prevent the splitting of the argument, so that PRINT_SIZE is expanded with a single argument (string{"ABCDEF",3}) (note that the parentheses are part of the argument).

Wintermute
  • 42,983
  • 5
  • 77
  • 80
  • 1
    +1 for the workaround. So would it not be good idea to update the C++11 preprocessor to treat {} the same as ()? – Barnett Apr 11 '15 at 14:29
  • 4
    I think a useful discussion of that question would be longer than even a large number of SO comments. It's a thought, but I wonder how much existing code would be broken by the change -- currently, braces can be simply passed as macro parameters. It doesn't just concern braces, either -- consider `SOME_MACRO(std::is_same::value)`, for example. – Wintermute Apr 11 '15 at 14:35
  • 3
    @Barnett, Preprocessor changes don't fly too well. Many would prefer that the preprocessor becomes obsolete from a feature that at least replaces its functionality without all the bad included in macros. AFAIK, the preprocessor is the exact same as C99, too, minus some small things that have more to do with C vs. C++ than the preprocessor itself. – chris Apr 11 '15 at 14:35
  • Parantheses don't work for me. Instead the original error i got `statement expression not allowed at file scope`. – Youda008 Jun 18 '21 at 13:12
8

Yes, there is a reason: The preprocessor is not aware of braces. It only respects string literals and parentheses, to other C/C++ language structures it is ignorant. As such, the call

PRINT_SIZE( string{"ABCDEF",3} );

is parsed as a macro invocation with two parameters string{"ABCDEF" and 3}. Since the macro PRINT_SIZE() expects only one parameter, the preprocessor bails out. Note that the C++ compiler has not even been invoked at this point!

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
0

What about this ?

#define SOME_BRACE  \
{                   \
    6, 8, 1, 3, 7,  \
    1, 4, 2, 0, 9   \
}

#define BRACE_SIZE(brace) (sizeof((int[]) SOME_BRACE) / sizeof(int))

int main(int argc, char *argv[])
{
    int array[BRACE_SIZE(SOME_BRACE)] = SOME_BRACE, i, s;

    for (i = 0, s = 0; i < BRACE_SIZE(SOME_BRACE); i++) {
        s = s + array[i];
    }
    /* 41 is expected */
    return s;
}
CyrIng
  • 343
  • 3
  • 8