下面的源代码摘自MongoDB。
我理解
detect_clone_factory_type_member_impl
的目的是检查类型 T
是否拥有 clone_factory_type
。然而,令我困惑的是,由于Derived
继承自T
和Fallback
,无论T
是否有clone_factory_type
,Derived
都应该拥有clone_factory_type
。这是否意味着应该始终选择第一个测试函数 No& test(typename U::clone_factory_type*)
?
当我实际进行实验时,这个例程按预期工作,这让我很好奇这个机制是如何运作的。我很欣赏各种专业人士的专业知识。
template <typename T>
struct detect_clone_factory_type_member_impl {
struct Fallback {
struct clone_factory_type {};
};
struct Derived : T, Fallback {};
using Yes = char[2];
using No = char[1];
template <typename U>
static No& test(typename U::clone_factory_type*);
template <typename U>
static Yes& test(U*);
static constexpr bool value = sizeof(test<Derived>(nullptr)) == sizeof(Yes);
using type = typename std::integral_constant<bool, value>::type;
};
这是 C++ 模板元编程中 SFINAE(替换失败不是错误)的经典示例。让我们分解一下代码的工作原理:
定义了主模板“detect_clone_factory_type_member_impl”,其中包含一个嵌套结构 Fallback 以及一个内部结构“clone_factory_type”。
在'detect_clone_factory_type_member_impl'内部,定义了另一个嵌套结构Derived,它继承自T和Fallback。
定义了两个使用别名,Yes 和 No。 Yes 是一个大小为 2 的数组,而 No 是一个大小为 1 的数组。
定义了两个名为test的重载静态成员函数:
一个版本采用指向类型 typename U::clone_factory_type* 的指针作为参数。 另一个版本采用指向任何类型 U* 的指针作为参数。 测试函数分别返回对 Yes 和 No 的引用。
定义了一个静态constexpr布尔变量值,通过调用sizeof(test(nullptr)) == sizeof(Yes)来初始化。这将检查 Derived 测试的重载决策是否成功。
类型别名定义为 std::integral_constant
现在,让我们考虑一下调用 test(nullptr) 时重载决策期间会发生什么:
如果 T 有一个嵌套类型clone_factory_type,那么 test 的第一次重载将是更好的匹配,因为 typename U::clone_factory_type* 比 U* 更好的匹配。因此,sizeof(test(nullptr)) 将等于 sizeof(Yes)。
如果T没有嵌套类型clone_factory_type,那么第一次重载的test将会因为SFINAE(Substitution Failure Is Not An Error)而被丢弃。在这种情况下,将选择 test 的第二个重载,并且 sizeof(test(nullptr)) 将等于 sizeof(No)。
根据sizeof(test(nullptr))的结果,设置value的值,判断T是否有嵌套类型clone_factory_type。
总而言之,这种机制允许编译器根据T是否具有嵌套类型clone_factory_type,使用SFINAE来选择测试函数的适当重载。
希望这对您有帮助。