Maarten Bamelis
Maarten Bamelis

Reputation: 2423

Deduce the type of a declaration

I am writing a macro that takes a declaration as its single argument. Is it possible to deduce the type of the declaration inside the macro without splitting up the single argument into separate type and identifier arguments?

#define M(declaration) \
    declaration;       \
    static_assert(sizeof(/* deduce type of 'declaration' */) == 4, "!")

M(int i);
M(double d{3.14});
M(std::string s{"Hello, world!"});

The following implementation would work but it feels less user-friendly (imo):

#define M(type, identifier) \
    type identifier;        \
    static_assert(sizeof(type) == 4, "!")

M(int, i);
M(double, d{3.14});
M(std::string, s{"Hello, world!"});

If possible, I would prefer to take the declaration as a single argument.


Related question: Macro to get the type of an expression; but I failed to get that code to work in my example (compiler error: expected nested-name-specifier).

Upvotes: 1

Views: 204

Answers (3)

eyelash
eyelash

Reputation: 3720

With C++20 you can use lambdas in unevaluated contexts to deduce the type of a declaration as the following GET_TYPE macro demonstrates:

template <class> struct GetMethodArgument;
template <class C, class T> struct GetMethodArgument<void(C::*)(T) const> {
    using type = T;
};
template <class T> using GetLambdaArgument = typename GetMethodArgument<decltype(&T::operator())>::type;
#define GET_TYPE(decl) GetLambdaArgument<decltype([](decl){})>

The way this works is that the macro creates a lambda with the declaration as an argument and the type of that lambda is then passed to some template machinery that extracts the type of the argument. I found this trick in the Carbon codebase.

You can now use that macro to solve your problem:

#define M(declaration) \
    declaration;       \
    static_assert(sizeof(GET_TYPE(declaration)) == 4, "!")

M(int i);
M(double d = 3.14);
M(std::string s = "Hello, world!");

Note that the macro only works for declarations that are valid function arguments, so it doesn't work for double d{3.14} for example.

Upvotes: 1

aschepler
aschepler

Reputation: 72356

This macro ought to work for all your examples, but it does have a nasty issue:

#define M(declaration) \
    declaration;       \
    do { \
        struct dummy__ { declaration; }; \
        static_assert(sizeof(dummy__) == 4, "!"); \
    } while (false)

The problem is that an initializer within a class definition must use either the = token or a braced-init-list at top level, and not parentheses at top level. So for example M(SomeClass obj(true, 3)); won't compile, even if sizeof(SomeClass)==4. Since braced initializers are not entirely equivalent to parenthesis initializers, this means some declarations would be impossible to use with the macro.

Upvotes: 1

If your static assertion message is really that simple "!"1, I suggest you ditch the preprocessor. Make the type system work for you instead:

namespace detail {
  template<typename T>
  struct check_declared_type {
    using type = T;
    static_assert(sizeof(type) == 4, "!");
  };
}

template<typename T>
using M = typename detail::check_declared_type<T>::type;

// .. Later

int main() {
  M<int> i;
  M<double> d{3.14};
  M<std::string> s{"Hello, world!"};
}

1 - Specifically, if you don't need the preprocessor to stringify anything for you.

Upvotes: 2

Related Questions