我知道 C++ 不支持反射,但是我通过模板元编程的方式通过论文反射支持,但不明白这是如何实现的。有人有更多关于如何使用模板元编程在 C++ 中实现这一点的详细信息或示例吗?
2 回答
下面是一个在编译时测试 Obj 类型的 Type 是否具有名为“foo”的 Type 类型的公共数据成员的结构示例。它使用 C++11 特性。虽然可以使用 C++03 特性来完成,但我认为这种方法更优越。
首先,我们使用 std::is_class 检查 Obj 是否是一个类。如果它不是一个类,它就不能有数据成员,所以测试返回 false。这是通过下面的部分模板专业化实现的。
我们将使用 SFINAE 来检测对象是否包含数据成员。我们声明了具有类型模板参数“指向类 Obj 类型的数据成员的指针”的结构助手。然后我们声明静态函数测试的两个重载版本:第一个,它返回一个指示失败测试的类型,通过省略号接受任何参数。请注意,省略号在重载分辨率中的优先级最低。第二个,它返回一个指示成功的类型,接受一个指向带有模板参数 &U::foo 的辅助结构的指针。现在我们检查使用绑定到 Obj 的 U 的测试调用如果使用 nullptr 和 typedef 调用返回到 testresult。编译器首先尝试第二个版本的测试,因为省略号是最后尝试的。如果助手<&Obj::foo> 是一个合法类型,仅当 Obj 具有类型类型的公共数据成员时才为真,然后选择此重载并且 testresult 将为 std::true_type。如果这不是合法类型,则从可能的候选列表(SFINAE)中排除重载,因此将选择接受任何参数类型的剩余测试版本,并且测试结果将为 std::false_type。最后,将 testresult 的静态成员值分配给我们的静态成员值,这表明我们的测试是否成功。
该技术的一个缺点是您需要明确知道您正在测试的数据成员的名称(在我的示例中为“foo”),因此要为不同的名称执行此操作,您必须编写一个宏。
您可以编写类似的测试来测试类型是否具有具有特定名称和类型的静态数据成员,是否具有具有特定名称的内部类型或 typedef,是否具有具有特定名称的成员函数,可以使用给定的参数类型等等,但这超出了我现在的时间范围。
template <typename Obj, typename Type, bool b = std::is_class<Obj>::value>
struct has_public_member_foo
{
template <typename Type Obj::*>
struct helper;
template <typename U>
static std::false_type test(...);
template <typename U>
static std::true_type test(helper<&U::foo> *);
typedef decltype(test<Obj>(nullptr)) testresult;
static const bool value = testresult::value;
};
template <typename Obj, typename Type>
struct has_public_member_foo<Obj, Type, false> : std::false_type { };
struct Foo
{
double foo;
};
struct Bar
{
int bar;
};
void stackoverflow()
{
static_assert(has_public_member_foo<Foo, double>::value == true, "oops");
static_assert(has_public_member_foo<Foo, int>::value == false, "oops");
static_assert(has_public_member_foo<Bar, int>::value == false, "oops");
static_assert(has_public_member_foo<double, int>::value == false, "oops");
}
可以在编译时查询类型的某些特征。最简单的情况可能是内置sizeof
运算符。正如 MadScientist 发布的那样,您还可以探查特定成员。
在使用通用编程或模板元编程的框架内,通常有关于类概要的合同(形式化为 概念)。
例如,STL 对函数对象使用成员 typedef result_type
。
boost:result_of
(后来成为std::result_of
)扩展了这个契约以允许嵌套类模板,以便计算参数是泛型的函数对象的结果类型(换句话说 - 具有重载或模板operator()
)。然后boost:result_of
将执行编译时反射,以允许客户端代码确定函数指针、STL 函数对象或“模板化函数对象”的结果类型,统一允许编写在任何一种情况下都“正常工作”的更通用代码。旁注:在这种特殊情况下,C++11 可以做得更好——我用它作为示例,因为它既不平凡又基于广泛的组件。
此外,可以使用客户端代码,该代码将发出一个数据结构,该结构包含在注册特定类型时在编译时推导出的元信息(甚至由客户端代码传入)。框架代码可以例如使用
typeid
运算符来获取类型的运行时表示,并为特定的构造函数、析构函数和一组成员函数(有些可能是可选的)生成调用存根,并将此信息存储在std::map
keyed by std::type_index
(或std::type_info
旧版本语言的手写包装器)。在程序的稍后时间点,可以在给定某些对象类型(运行时表示)的情况下找到该信息,以便运行一种算法,例如创建更多相同类型的实例,其中一些实例具有临时生命周期,运行一些操作并整理。
将这两种技术结合起来非常强大,因为可以在编译时使用积极的内联来生成要以高复杂度运行的代码,这些代码可能是动态模板生成的许多变体,在程序运行时通过类似的方式与时间要求不高的部分进行交互时间。