25

Say we have an implementation of std::aligned_storage. I've defined two macros for the alignof and alignas operators.

#include <iostream>
#include <cstddef>

#define ALIGNOF(x) alignof(x)
#define ALIGNAS(x) alignas(x)

template<std::size_t N, std::size_t Al = ALIGNOF(std::max_align_t)>
struct aligned_storage
{
    struct type {
        ALIGNAS(Al) unsigned char data[N];
    };
};

int main()
{
    // first case
    std::cout << ALIGNOF(aligned_storage<16>::type); // Works fine

    // second case
    std::cout << ALIGNOF(aligned_storage<16, 16>::type); // compiler error
}

In the second case I get the error in the title of the question (compiling with Clang, similar error with GCC). The error is not present if I replace the macros with alignof and alignas respectively. Why is this?

Before you start asking me why I'm doing this - the original macros have C++98 compatible code such as __alignof and __attribute__((__aligned__(x))) and those are compiler specific, so macros are my only choice...

EDIT: So according to the question marked as duplicate, an extra set of parenthesis would fix the issue.

std::cout << ALIGNOF((aligned_storage<16, 16>::type)); // compiler error

It doesn't. So, how would I go about doing this? (Satisfiable question?)

DeiDei
  • 10,205
  • 6
  • 55
  • 80
  • 1
    @melpomene, That doesn't really give any way of working around the problem – chris Jun 25 '16 at 15:44
  • 1
    Why? Because comma. - Macro expansion sees the comma before making syntactic sense of any other content – Hagen von Eitzen Jun 25 '16 at 15:44
  • http://stackoverflow.com/questions/679979/how-to-make-a-variadic-macro-variable-number-of-arguments may be helpful – Hagen von Eitzen Jun 25 '16 at 15:46
  • 1
    btw - simply use `#define MACRO(...) something(__VA_ARGS__)` to work around it. –  Jun 25 '16 at 15:47
  • The extra parens thing only works for expressions. You're dealing with types/parameter lists, so a different workaround is required. – melpomene Jun 25 '16 at 15:49
  • Why is the marked duplicate **correct** as a duplicate for your question? The assessment provided in that answer was exactly the same for your situation: you have a comma in the macro invocation. The solution for your answer happens to be different than that given in the duplicate because the macro invocations appear in 2 different syntactical areas of the code. While you couldn't use the solution from the duplicate, the assessment (of comma confusion) should have been more than sufficient. That's why it's correct to mark it as a duplicate. – Michael Gaskill Jun 25 '16 at 20:11

2 Answers2

33

C/C++ preprocessor is not aware of any C/C++ language constructs, it is just text preprocessor with its own syntax and rules. According to that syntax the following code ALIGNOF(aligned_storage<16, 16>::type) is invocation of macro ALIGNOF with 2 arguments (aligned_storage<16 and 16>::type) because there is comma inside parentheses.

I would suggest you to typedef aligned_storage<16, 16> and use that type inside this macro invocation.

melpomene
  • 84,125
  • 8
  • 85
  • 148
mvidelgauz
  • 2,176
  • 1
  • 16
  • 23
  • @melpomene Thank you for editing, I didn't know ** ** doesn't work inside ` ` :) and my reaction was slower than yours :) – mvidelgauz Jun 25 '16 at 15:51
12

As has been explained, macro arguments are separated by commas that are not within extra parentheses. There are a few easy-ish ways to get around this, some more general than others:

  1. Use a variadic macro (or a corresponding compiler extension for C++98) (live example):

    #define ALIGNOF(...) alignof(__VA_ARGS__)
    ALIGNOF(aligned_storage<16, 16>::type)
    
  2. Get the caller to pass the number of arguments and wrap the arguments in extra parentheses (live example):

    #define ALIGNOF(n, tuple) alignof(BOOST_PP_TUPLE_ENUM(n, tuple))
    ALIGNOF(2 (aligned_storage<16, 16>::type))
    
  3. Get the caller to pass in a "sequence" (live example):

    #define ALIGNOF(seq) alignof(BOOST_PP_SEQ_ENUM(seq))
    ALIGNOF((aligned_storage<16)(16>::type))
    
  4. If the argument is a type, use a typedef (live example):

    typedef aligned_storage<16, 16>::type storage_t;
    ALIGNOF(storage_t)
    

    Note that templated aliases can be created prior to C++11 by templating a struct and exposing a type member:

    template<int N> 
    struct alias {
        typedef typename aligned_storage<N, N>::type type;
    };
    
  5. If the argument can be used parenthesized, get the caller to wrap the argument in parentheses and use it directly. That is not the case with alignof.

chris
  • 60,560
  • 13
  • 143
  • 205