C中的类可以有多个基类,所以没有必要使用“get me the base”trait。 然而,TR2增加了新的编译器支持的trait std::tr2::bases和std::tr2::direct_bases,它们返回基类的不透明类型列表。 我不确定这是否会成为C14,或者它是否会独立发布,但GCC已经seems to support this了。
#include <typeindex>
#include <utility>
template <typename T>
struct tag
{
using type = T;
};
namespace impl {
// Don't need an ADL dummy for adl_ViewBase since we
// only ever call it from inside impl::
template <typename D, std::size_t I>
struct BaseViewer {
friend constexpr auto adl_ViewBase(BaseViewer);
};
template <typename D, std::size_t I, typename B>
struct BaseWriter
{
friend constexpr auto adl_ViewBase(BaseViewer<D, I>) {return tag<B>{};}
};
}
// Do need an ADL dummy for adl_RegisterBases somewhere
// SFINAE disables all the 'active' definitions, and the compiler needs
// to find *something* or it's a hard error, not SFINAE
template <typename T>
constexpr void adl_RegisterBases(void *) {}
template <typename T>
struct Base
{
template <
typename D,
std::enable_if_t<std::is_base_of_v<T, D>, std::nullptr_t> = nullptr,
typename impl::BaseWriter<D, /*???*/, T>::nonExistent = nullptr
>
friend constexpr void adl_RegisterBases(void *) {}
};
//-----------------------
//Sample object hierarchy for testing
struct A : Base<A> {};
struct B : Base<B>, A {};
struct C : Base<C> {};
struct D : Base<D>, B, C {};
int main () {
//ADL hint: by casting to (A *), we tell the compiler to check
//scopes related to A for possible overloads of adl_RegisterBases
//Without the cast, the compiler just immediately instantiates our
//dummy without actually checking relevant templates.
//Casting nullptr to (A*) provides the hint without constructing A
adl_RegisterBases<A>((A *)nullptr);
}
5条答案
按热度按时间fnx2tebb1#
C中的类可以有多个基类,所以没有必要使用“get me the base”trait。
然而,TR2增加了新的编译器支持的trait
std::tr2::bases
和std::tr2::direct_bases
,它们返回基类的不透明类型列表。我不确定这是否会成为C14,或者它是否会独立发布,但GCC已经seems to support this了。
ogsagwnx2#
我想
std::is_base_of
可以帮助你字符串
如果D是从B派生的,或者两者都是同一个非联合类,则提供等于true的成员常数值。否则value为false。
你可以用它来检查一个类是否是另一个类的基类:
型
vhmi4jdf3#
这可能是一种很好的方法,具体取决于您的用例。在基类本身中声明名为
base
的基类的typedef。然后派生类
X
将继承它作为类型名X::base
。所以
B::base
是A
,C::base
是A
。字符串
ldxq2e6h4#
在一定的限制下,这是可能的!
在gcc.godbolt.org上运行
字符串
事情是这样的:
friend
函数。这些函数被SFINAE设置为不可调用,但是在重载解析时只要考虑它们就会示例化模板,该模板会将相应的基类追加到列表中。k2fxgqgv5#
@HolyBlackCat的回答很精彩,但不透明。这是我试图解释它。
通过程序一步一步地工作:
friend
类中的函数可以在类中声明 * 和定义 *。这实质上是将一个 * 非成员 * 函数添加到类的作用域中。friend
函数,我们可以访问用于定义该专门化的特定模板参数,并且我们可以将这些参数放在编译器和其他模板可以看到的地方。字符串
现在,通过将
BaseWriter
的示例放入我们想要检测的D
的基类中,我们可以使编译器将adl_ViewBase
的相应重载添加到类作用域中,该作用域将基类的类型编码为函数的返回类型。adl_ViewBase
并强制ADL创建一个候选池,该池由我们选择的函数组成。inline
,因此如果它们具有相同的名称和调用签名,它们将被视为违反ODR。我们可以通过为每个特定的模板专用函数签名提供一个正向声明来恢复“正常”行为,这是通过在具有类似模板参数的模板中提供声明最容易实现的。型
I
的唯一值传递到各种BaseWriter
中,并且(B)跟踪有效值的总数,我们可以通过查询viewBase
的返回类型来系统地检索所有函数(以及所有类型标记)。BaseViewer
充当允许我们查看特定索引库的键。(因此命名。)像一个Map,而不像一个数组或列表,我们必须处理为我们的数据自己创建唯一的键。不过,那是以后的事了。现在...friend
函数是模板专门化的一部分,因此模板示例不一定需要实际构造来填充数组中它的部分。然后,我们可以利用C++规范的某个特性,该特性与编译器必须为哪些可能的专门化实际生成代码有关。如cppreference.com,当代码在需要函数定义存在的上下文中引用函数时,或者如果定义的存在影响程序的语义(自C++11起),并且此特定函数尚未显式示例化,则会发生隐式示例化。如果可以从上下文推导出模板参数列表,则不必提供该列表。
函数定义的存在被认为会影响程序的语义,如果函数是表达式 * 需要常量求值 , 即使 * 表达式的常量求值不是必需的,或者如果常量表达式求值不使用定义。
(强调我的)。特别是,非类型模板参数总是被标准认为是“明显的常量求值表达式”,因此将
BaseWriter
的特定特化包含到模板参数中足以迫使编译器为其生成代码(从而产生友元函数,从而填充我们的“Map”),即使模板最终未被使用。型
在这里,
T
表示我们的一个基本类型,它本身将以CRTP方式继承自Base
。D
是我们要查询的派生类。就像普通的&&
和||
一样,如果模板替换遇到替换失败,则会短路,因此如果D没有派生T,编译器会停止其替换尝试。只有当enable_if
通过时,它才会尝试示例化最后一个参数,这会导致编译器将T
追加到我们正在构建的数组中。nullptr
用作忽略参数的通用单元类型。Base
派生的类CRTP时,编译器都会自动将一个对应的registerBases
函数模板添加到封闭作用域中。T, D
的任何值,BaseWriter
实际上都没有名为nonExistent
的成员,因此替换总是失败。因此,在最终运行时中不会有直接对应于registerBases
的代码。adl_RegisterBases
以记录我们的意图,并将我们得到的所有内容放在一起:型
现在,我们要做的就是弄清楚我们最后留下的那一点:以一种系统的方式给每个碱基分配不同的数字,我们可以在最后复制。最简单的方法是以某种方式跟踪每个目标(派生类)的“map”的大小,并将map的最新大小作为其索引分配给每个新基类。
所以,我们需要一个基本情况和一般结构的东西,看起来像...
型
一个特定的
NumBases
规范只能有一个::value
,这是一个静态属性,在编译过程中不能改变;如果我们打算使用类型模板NumBases
来跟踪我们的Map的增长长度,这是一个问题。因此,我们构建NumBases
,将其本身表示为特定的basetype-index对<Next, I>
,并通过将::value
传递到BaseWriter
来将其附加到特定的BaseViewer
,具体为Base
:型
至此,我们的代码已经运行并编译完毕,可以开始学习C++ Insights了。
请注意,
BaseWriter
生成了一个特殊化BaseWriter<A,0,A>
,NumBases
生成了一个特殊化NumBases<A, A, 0>
。专门化BaseWriter<A,0,A>
又生成了BaseViewer<A, 0>
和一个专门接受BaseViewer<A, 0>
的viewBase
重载。我们可以利用我们的老朋友SFINAE:我们可以对NumBases
进行特殊化,作为一个特殊化,它总是首先被检查,但只有当具有正确索引值I
的相应BaseViewer
(忽略Next
)已经被特殊化时,才会通过替换:型
如果我们递归地定义
NumBases
本身,但是用I+1
替换I
,那么当特殊化停止应用时,递归将终止--也就是说,当编译器用完了具有适当索引的现有BaseViewer
时。作为一个副作用,通过首先填充我们的“map”的相同机制,ADL的考虑也将示例化BaseViewer<D, I+1>
,增加计数器并为下一次调用NumBases{}
做好准备。为了清楚起见,我们将SFINAE逻辑移到它自己的模板类型参数中,因此
NumBases
现在定义为型
这就是最难的部分:我们可以通过C++ Insights确认
NumBases
完成了它的工作,并为我们提供了从BaseViewer<D, 0>
到BaseViewer<D, 3>
以及从BaseWriter<D, 0, A>
到BaseWriter<D, 3, D>
的密钥(用于名为D
的测试结构)。我们使用std::index_sequence
和参数包扩展来枚举我们的“map”的条目,将其打包到type_list
中:型
......清理我们的代码一些(打包直接调用
adl_RegisterBases
,其非常具体的<T>((T *)nullptr)
语法,到自己的函数;创建一个helper structimpl::RegisterBases
,它隐藏了一些在Base
中调用的实现细节-这意味着我们必须将adl_RegisterBases
本身移动到impl
中,以便来自RegisterBases
的调用可以找到它):型
...并消除了GCC关于我们的非模板化友元函数的一些警告(可能正是警告我们在第二步中必须处理的奇怪的
inline
失败行为)。然后代码运行。要使用它,请使用CRTP将样板
adl_RegisterBases
friend模板包含在您想要检测的所有碱基中,然后对于任何类型的T
,只需获取base_list<T>
的类型。在gcc.godbolt.org上运行
型