C++ preprocessors are not aware of template arguments?
Asked Answered
R

4

5

As it appears, C++ preprocessor fails if a template instantiation with multiple arguments passed to a macro as an argument.

See an example below.

#include <stdio.h>

#define FOO(v) printf("%d\n",v::val())

template<int N>
struct bar {
    static int val() { return N; }
};
template<int N, int M>
struct baz {
    static int val() { return N+M; }
};

int main() {
    printf("%d\n",bar<1>::val());
    printf("%d\n",baz<1,2>::val());
    FOO(bar<10>);       // OK
    FOO(baz<20,30>);    // error: too many arguments provided to function-like macro invocation
    FOO((baz<20,30>));  // error: '::val' has not been declared
}

Tested with clang++ and g++

Should it be considered as a bug?

Radiogram answered 27/8, 2015 at 5:49 Comment(5)
You could design the macro to extract the inside of a parenthesized argument, but this example certainly doesn't need a macro.Synonymous
A related question: Are C++ preprocessors aware of C++? AFAICT the C++ preprocessor hasn't changed much at all since it was only the C preprocessor... ;)Selenium
@chris, thanks, with the wording from your message I found an answer to a similar question https://mcmap.net/q/172796/-comma-in-c-c-macro/…Radiogram
that is a nice link, thanks for reposting itValerianaceous
The preprocessor knows nothing about C++ syntax, it's a very dumb macro processor that follows very simple rules.Tendentious
M
3

The C/C++ preprocessor recognizes commas as macro argument separators unless they are nested inside parentheses. Just parentheses. Brackets, braces and template markers don't count:

The individual arguments within the list are separated by comma preprocessing tokens, but comma preprocessing tokens between matching inner parentheses do not separate arguments. (C++14 §16.3/11; C11 §6.10.3/11)

(A side effect of the above is that you can use unbalanced braces and brackets as macro arguments. That's usually not a very good idea, but you can do it if you have to.)

Problems occasionally crop up as a result; a common one is unwanted multiple arguments when the argument is supposed to be a block of code:

MY_FANCY_MACRO(1000, { int i=0, j=42; ... })

Here, the macro is called with (at least) 3 arguments, although it was probably written to accept 2.

With modern C++ (and C) compilers, you have a few options. In a fairly subjective order:

  1. Rewrite the macro as an inline function. If the argument is a code block, consider using a templated function which could accept a lambda or other functor. If the argument is a type, make it a template argument instead.

  2. If surrounding the argument with redundant parentheses is syntactically valid, do that. But in such a case it is almost certainly the case that suggestion (1) above would have worked.

  3. Define:

    #define COMMA ,
    

    and use it where necessary:

     FOO(baz<20 COMMA 30>);
    

    This doesn't require modifying the macro definition in any way, but it will fail if the macro passes the argument to another macro. (The replacement will be done before the inner macro call is parsed, so the multiple argument problem will just be deferred to the inner call.)

  4. If you expect that one macro argument might contain unprotected commas, and it is the last or only argument, and you're in a position to modify the macro, and you're using C++11/C99 or better (or gcc, which has allowed this as an extension for some time), make the macro variadic:

    #define FOO(...) printf("%d\n",__VA_ARGS__::val())
    
Mesencephalon answered 28/8, 2015 at 2:36 Comment(0)
V
11

No, it's not a bug.

The c preprocessor is a different beast from the rest of the language and it plays by its own rules. Changing this would break compatibility in a massive way, CPP is highly rigorously standardized.

The usual way to work around these comma issues is,

typedef baz<20,30> baz2030_type;
FOO(baz2030_type);
Valerianaceous answered 27/8, 2015 at 6:13 Comment(0)
M
3

The C/C++ preprocessor recognizes commas as macro argument separators unless they are nested inside parentheses. Just parentheses. Brackets, braces and template markers don't count:

The individual arguments within the list are separated by comma preprocessing tokens, but comma preprocessing tokens between matching inner parentheses do not separate arguments. (C++14 §16.3/11; C11 §6.10.3/11)

(A side effect of the above is that you can use unbalanced braces and brackets as macro arguments. That's usually not a very good idea, but you can do it if you have to.)

Problems occasionally crop up as a result; a common one is unwanted multiple arguments when the argument is supposed to be a block of code:

MY_FANCY_MACRO(1000, { int i=0, j=42; ... })

Here, the macro is called with (at least) 3 arguments, although it was probably written to accept 2.

With modern C++ (and C) compilers, you have a few options. In a fairly subjective order:

  1. Rewrite the macro as an inline function. If the argument is a code block, consider using a templated function which could accept a lambda or other functor. If the argument is a type, make it a template argument instead.

  2. If surrounding the argument with redundant parentheses is syntactically valid, do that. But in such a case it is almost certainly the case that suggestion (1) above would have worked.

  3. Define:

    #define COMMA ,
    

    and use it where necessary:

     FOO(baz<20 COMMA 30>);
    

    This doesn't require modifying the macro definition in any way, but it will fail if the macro passes the argument to another macro. (The replacement will be done before the inner macro call is parsed, so the multiple argument problem will just be deferred to the inner call.)

  4. If you expect that one macro argument might contain unprotected commas, and it is the last or only argument, and you're in a position to modify the macro, and you're using C++11/C99 or better (or gcc, which has allowed this as an extension for some time), make the macro variadic:

    #define FOO(...) printf("%d\n",__VA_ARGS__::val())
    
Mesencephalon answered 28/8, 2015 at 2:36 Comment(0)
S
1

The macro's argument is treated as plain text string and the arguments are separated using commas. Hence the comma in the template will be treated as a delimiter. Thus the preprocessor will think that you have passed on two arguments to a single argument macro, hence the error.

Steve answered 27/8, 2015 at 9:10 Comment(0)
A
0

BOOST_IDENTITY_TYPE is the solution for that: https://www.boost.org/doc/libs/1_73_0/libs/utility/identity_type/doc/html/index.html

You can also wrap the type into decltype: decltype(std::pair<int, int>()) var; which also adds an extra parentheses, but this unfortunately does an extra ctor call.

Atreus answered 8/12, 2022 at 13:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.