我想使用宏创建自己的typeid
。例如,如果我致电MY_TYPEID(token)
,我希望它解析为
my_type_info<token>()
如果是类型,
my_type_info(token)
如果它是一个值。
有没有办法在C ++中做到这一点?
答案 0 :(得分:2)
这很......很难。
这是完成的宏:
#define MY_TYPEID(...) \
[&](auto...) { \
\
auto &&thing(__VA_ARGS__); \
auto probe = [](auto...) -> decltype(thing, void()) {}; \
\
if constexpr(detail_type_info::wizz(probe)) \
return detail_type_info::my_type_info( \
std::forward<decltype(thing)>(thing) \
); \
else \
return detail_type_info::my_type_info< \
my_decltype(__VA_ARGS__) \
>(); \
}()
这个有趣的Gizmo依赖于与this other answer of mine相同的基本原则:thing
是转发引用或函数声明,具体取决于参数是表达式还是类型。
thing
作为参考的情况很简单(ed):它最终将作为转发参数传递给my_type_info
,这将从那里取出。
thing
是一个函数的情况很有意思:它有一个推导的返回类型,但尚未定义(也不会定义)。因此,在提供定义之前不可能使用它。这个“使用”包括普通的使用,例如普通的thing;
:只是试图将它放入表达式会使程序格式不正确。
通过一层SFINAE检测到此特征:probe
是一个通用lambda,其返回类型使用thing
。但由于它是通用的,在我们调用 lambda之前,这实际上并没有爆发。这正是detail_type_info::wizz
尝试做的事情:
namespace detail_type_info {
template <class F>
constexpr auto wizz(F probe) -> decltype(probe(), true) { return true; }
constexpr auto wizz(... ) -> decltype( false) { return false; }
}
detail_type_info::wizz(probe)
尝试匹配其中一个重载。第一个重载尝试在未评估的上下文中调用probe
,实例化probe
(lambda)的调用操作符。如果thing
确实等待推断其返回类型,则此实例化失败并且整个重载都是SFINAE。第二个重载没有这样的事情,并且总是有效但从未因...
而优先考虑。
因此我们现在可以通过detail_type_info::wizz(probe)
告诉我们宏的参数是类型(false
)还是表达式(true
)。这由if constexpr
启用,通过使最外面的lambda成为模板而使其有效。
最后一个障碍:detail_type_info::my_type_info(std::forward<decltype(thing)>(thing))
分支中的true
始终有效(即使在thing
作为函数声明的情况下实例化时会中断)。
然而,false
分支不能以return detail_type_info::my_type_info<__VA_ARGS__>()
的天真方式调用,因为当__VA_ARGS__
是一个非有效的非类型表达式时,这可能会变成无意义模板参数(例如double
),在这种情况下编译器会立即生效。
这就是为什么我重用another of my answers我实现my_decltype
的地方,decltype
表达式和无操作类型,因此总是形成一个有效的函数调用。
有了所有这些机制,并添加了两个my_type_info
存根,以下内容:
namespace detail_type_info {
template <class T>
void my_type_info(T &&) {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
template <class T>
void my_type_info() {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
}
int main() {
MY_TYPEID(int);
MY_TYPEID(4.2);
}
...按预期输出:
void detail_type_info::my_type_info() [with T = int]
void detail_type_info::my_type_info(T&&) [with T = double]
完整代码:
namespace detail_typeOrName {
struct probe {
template <class T>
operator T() const;
};
template <class T>
T operator * (T const &, probe);
probe operator *(probe);
}
#define my_decltype(x) decltype((x) * detail_typeOrName::probe{})
namespace detail_type_info {
template <class T>
void my_type_info(T &&) {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
template <class T>
void my_type_info() {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
template <class F>
constexpr auto wizz(F probe) -> decltype(probe(), true) { return true; }
constexpr auto wizz(... ) -> decltype( false) { return false; }
}
#define MY_TYPEID(...) \
[&](auto...) { \
\
auto &&thing(__VA_ARGS__); \
auto probe = [](auto...) -> decltype(thing, void()) {}; \
\
if constexpr(detail_type_info::wizz(probe)) \
return detail_type_info::my_type_info( \
std::forward<decltype(thing)>(thing) \
); \
else \
return detail_type_info::my_type_info< \
my_decltype(__VA_ARGS__) \
>(); \
}()