如何避免简单的递归模板类型定义
How to avoid simple recursive template typedefs
我有以下简单的问题:一类template<typename D> Parser
,它将一个ModuleType
定义为Module<Parser>
。我想注入解析器类型到模块中,以便能够再次从解析器中提取几种类型。这很方便,因为在Module中只需要一个模板参数。但是,如果解析器需要在模块中定义的一些类型(如OptionsType
),那么问题就来了,通过使用声明using ModuleOptions = ...
在Parser
中访问此类型显然不适用于派生类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对你的问题的评论已经回答了这是模板实例化的有效方法,并且你可以看到这是多个编译器所做的。
你必须重新设计。在这种特殊情况下,我认为一种非常可行的方法是模仿标准库(一点)并提供解析器trait类。您可以将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>
,当传递B
类型的左值时,A<B>::f(A<B> &)
应该是可调用的。然而,这并不能阻止编译器抱怨g
的声明,clang的错误消息相当显式地调用A<B>
和B
不相关的类型:
错误:类型' a '的非const左值引用不能绑定到不相关类型'B'的值
这里也会发生这种情况,因为B
只有在完成A<B>
的实例化后才会被称为A<B>
的派生。
我想出了一个简单有效的解决方案来规避上述类型定义问题:这是一个稍微复杂一点的例子,它使用ParserTraits
结构体来定义Parser
和模块ModuleA,ModuleB
中需要的所有类型。现在也可以在ModuleA
类中使用已定义的ModuleB
,并且Parser
可以访问所有Module#Options
类型…
代码如下:https://ideone.com/nVWfp6
template<typename ParserTraits>
struct ModuleA {
using ParserType = typename ParserTraits::ParserType;
using DType = typename ParserTraits::DType;
using OptionsType = int;
using ModuleBType = typename ParserTraits::ModuleBType;
using ModuleBOptions = typename ModuleBType::OptionsType;
void foo(){
std::cout << "ModuleA::foo: ParserType: " << typeid(ParserType).name() << std::endl;
std::cout << "ModuleA::foo: ModuleBType: " << typeid(ModuleBType).name() << std::endl;
std::cout << "ModuleA::foo: ModuleBOptions: " << typeid(ModuleBOptions).name() << std::endl;
}
};
template<typename ParserTraits>
struct ModuleB {
using ParserType = typename ParserTraits::ParserType;
using DType = typename ParserTraits::DType;
using OptionsType = float;
using ModuleAType = typename ParserTraits::ModuleAType;
using ModuleAOptions = typename ModuleAType::OptionsType; //uncomment this!!
void foo(){
std::cout << "ModuleB::foo: ParserType: " << typeid(ParserType).name() << std::endl;
std::cout << "ModuleB::foo: ModuleAType: " << typeid(ModuleAType).name() << std::endl;
std::cout << "ModuleB::foo: ModuleAOptions: " << typeid(ModuleAOptions).name() << std::endl;
}
};
// The PARSER TYPE TRAITS Struct!!
template<typename Parser,typename D>
struct ParserTraits {
using DType = D;
using ParserType = Parser;
using ModuleAType = ModuleA<ParserTraits>;
using ModuleBType = ModuleB<ParserTraits>;
};
template<typename D, typename Derived = void >
struct Parser {
using DType = D;
// Inject the derived class as the parser class for the modules
using DerivedType = typename std::conditional< std::is_same<Derived,void>::value, Parser, Derived>::type;
using ParserTraitsType = ParserTraits<DerivedType,DType>;
using ModuleAType = ModuleA<ParserTraitsType>;
using ModuleBType = ModuleB<ParserTraitsType>;
using ModuleAOptions = typename ModuleAType::OptionsType; //uncomment this!!
using ModuleBOptions = typename ModuleBType::OptionsType; //uncomment this!!
virtual void foo(){
std::cout << "Parser::foo" << std::endl;
ModuleAType a;
a.foo();
ModuleBType b;
b.foo();
}
};
template<typename D>
struct ParserGUI: Parser<D, ParserGUI<D> > {
using Base = Parser<D, ParserGUI<D> >;
void foo(){
std::cout << "ParserGUI::foo" << std::endl;
typename Base::ModuleAType a;
a.foo();
typename Base::ModuleBType b;
b.foo();
}
};
int test() {
std::cout << "SceneParser1" << std::endl;
Parser<double> t;
t.foo();
ParserGUI<double> d;
d.foo();
ParserGUI<double> r;
ParserGUI<double>::Base & base = r;
base.foo();
}
- 有没有一种方法可以通过"typedef"为重新定义的基本类型定义特征和强制转换运算符
- 强枚举类型定义:Clang Bug 还是 C++11 标准不确定性?
- 列表参数的类型定义
- 使用模板化的键类型定义 std::map,该键类型基于作为参数接收的函数
- 关于 C++ 中的函数类型定义
- C++(和 ROS) - 包含与前向声明引用,设置默认值和类型定义
- 将使用/类型定义限制为类范围
- 模板类型定义?
- C++:模板类的类型定义
- 如何对命名空间限定类型进行类型定义?
- 此递归模板类型定义是否有效C++?
- 具有调整对齐方式的类型定义
- C++从抽象类型定义类成员
- 用于C++代码的 API 监视器类型定义 (XML)
- 如何将result_of与函数类型定义一起使用
- 在C++的适当类型定义位置
- 如何根据模板类型定义浮点常量?
- 如何为缺少预定义运算符而不扩展命名空间"std"的标准类型定义运算符>> (istream &, ...)?
- 参数化类的别名(或类型定义)内部类
- 如果我想从类型"T"定义元素的容器(来自 STL),那么"T"必须使用默认构造函数?