我有以下简单的问题:
A类template<typename D> Parser
它定义了一个 ModuleType
作为Module<Parser>
.我想将解析器类型注入(inject)模块,以便能够从其中的解析器中再次提取几种类型。这很方便,因为 Module 中只需要一个模板参数。但是如果解析器需要一些在模块中定义的类型,例如 OptionsType
,问题就来了。 , 在 Parser
中访问它通过使用声明 using ModuleOptions = ...
显然不适用于派生类的实例化 ParserDerived
.错误:error: no type named ‘DType’ in ‘struct ParserDerived<double>’ using DType = typename Parser::DType;
所以不知何故类型
我害怕使用这样的模式,因为我可能会在未来意识到我使用这些模式的所有构造都会崩溃成大量难以理解的编译器故障......
解决以下问题的更好方法是什么?
#include <iostream>
#include <type_traits>
using namespace std;
template<typename Parser>
struct Module{
using DType = typename Parser::DType;
using OptionsType = int;
};
template<typename D, typename Derived = void >
struct Parser{
using DType = D;
using DerivedType = typename std::conditional< std::is_same<Derived,void>::value, Parser, Derived>::type;
using ModuleType = Module<DerivedType>;
//using ModuleOptions = typename ModuleType::OptionsType; //uncomment this!!
};
template<typename D>
struct ParserDerived: Parser<D, ParserDerived<D> >{
using Base = Parser<D, ParserDerived<D> >;
using ModuleType = typename Base::ModuleType;
using DType = typename Base::DType;
};
int main() {
Parser<double> t;
ParserDerived<double> d;
}
最佳答案
这是发生了什么:
-
d
被定义为ParserDerived<double>
, 所以它被实例化- 基类给出为
Parser<double, ParserDerived<double>>
, 所以它被实例化-
DType
被定义为double
-
DerivedType
被定义为ParserDerived<double>
-
ModuleType
被定义为Module<ParserDerived<double>>
-
ModuleOptions
被定义为Module<ParserDerived<double>>::OptionsType
, 所以Module<ParserDerived<double>>
被实例化-
DType
被定义为ParserDerived<double>::DType
← 这里有错误 -
OptionsType
被定义为int
-
-
-
Base
被定义为Parser<double, ParserDerived<double>>
-
ModuleType
被定义为Parser<double, ParserDerived<double>>::ModuleType
-
DType
被定义为Parser<double, ParserDerived<double>>::DType
- 基类给出为
如果你画出这样的实例,很明显 DType
在定义之前使用。模板实例化必须像这样按顺序执行并不是很明显,但 dyp 对你的问题的评论已经回答了它是模板实例化的有效方式,你可以看到这是多个编译器所做的。
您将不得不重新设计。在这种特殊情况下,我认为一种非常可行的方法是(稍微)模仿标准库并提供一个解析器特征类。您将移动 ModuleType
的定义和 DType
那里,以便访问这些定义不需要解析器类的实例化。
回应您的评论:
您是否注释派生类的 DType
应该无关紧要因为不管它是否被定义都看不到,但这是一个很好的问题,为什么基类的 DType
没有被用在它的地方。 Parser<double, ParserDerived<double>>
正在实例化以便将其用作基类,但在该实例化期间它尚未被视为基类。执行实例化后,编译器将首先确保 Parser<double, ParserDerived<double>>
适合作为基类,才可以成为基类。
更短的例子可以更清楚地说明这一点:
template <class B> struct A {
static void f(A &);
static decltype(f(*(B*)0)) g();
};
struct B : A<B> { };
自 B
源自 A<B>
, A<B>::f(A<B> &)
当传递类型为 B
的左值时应该是可调用的.然而,这并不能阻止编译器提示 g
的声明。 ,并且 clang 的错误消息非常明确地调用了 A<B>
和 B
不相关的类型:
error: non-const lvalue reference to type 'A<B>' cannot bind to a value of unrelated type 'B'
这里也会发生这种情况,因为 B
仅因源自 A<B>
而为人所知在 A<B>
的实例化之后已完成。
关于c++ - 如何避免简单的递归模板类型定义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24839616/