我有以下简单问题:
课程template<typename D> Parser
将ModuleType
定义为Module<Parser>
。我想将解析器类型注入到模块中,以便能够从解析器中再次提取几种类型。这很方便,因为在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;
}
答案 0 :(得分:3)
以下是发生的事情:
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
不相关的类型:
错误:类型为“A&lt; B&gt;”的非const左值引用不能绑定到不相关类型'B'的值
此处也会发生这种情况,因为在B
的实例化完成后,A<B>
仅被称为从A<B>
派生而来。
答案 1 :(得分:0)
我提出了一个简单而有效的解决方案来规避上述typedef问题:
这是一个更复杂的示例,它使用ParserTraits
结构来定义Parser
和模块ModuleA,ModuleB
中所需的所有类型。现在也可以使用定义的ModuleB
ind ModuleA
类,Parser
也可以访问所有Module#Options
typedef ...
代码在这里: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();
}