我正在编写一个宏,它将声明作为其单个参数。是否可以在宏内部推导声明的类型,而不将单个参数拆分为单独的type和identifier参数?
#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!"});
以下实现可以工作,但感觉不太用户友好(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!"});
如果可能,我更愿意将声明作为单个参数。
相关问题: 获取表达式类型的宏;但我未能让该代码在我的示例中工作(编译器错误:预期的嵌套名称说明符)。
如果你的静态断言消息真的那么简单
"!"
1,我建议你放弃预处理器。让类型系统为您工作:
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 - 具体来说,如果您不需要预处理器为您字符串化任何内容。
这个宏应该适用于您的所有示例,但它确实有一个令人讨厌的问题:
#define M(declaration) \
declaration; \
do { \
struct dummy__ { declaration; }; \
static_assert(sizeof(dummy__) == 4, "!"); \
} while (false)
问题是类定义中的初始化器必须在顶层使用
=
标记或花括号初始化列表,而不是顶层的括号。例如,即使 M(SomeClass obj(true, 3));
,sizeof(SomeClass)==4
也不会编译。由于大括号初始值设定项并不完全等同于括号初始值设定项,这意味着某些声明无法与宏一起使用。
使用 C++20,您可以在未计算的上下文中使用 lambda 来推断声明的类型,如以下
GET_TYPE
宏所示:
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){})>
其工作方式是,宏创建一个 lambda,并将声明作为参数,然后将该 lambda 的类型传递给某个提取参数类型的模板机制。我在 Carbon 代码库中发现了这个技巧。
您现在可以使用该宏来解决您的问题:
#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!");
请注意,该宏仅适用于有效函数参数的声明,因此它不适用于 double d{3.14}
。