Reputation: 2423
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
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
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
Reputation: 170084
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