问题
I have the following simple problem:
A class template<typename D> Parser
which defines a ModuleType
as Module<Parser>
. I would like to inject the parser type into the module, as to be able to extract again several types from the parser in it. This is handy as one needs only one template parameter in Module. But the problem comes if the parser needs some types which are defined in the module such as OptionsType
, accessing this in the Parser
by the using declaration using ModuleOptions = ...
obviously does not work for an istantiation of the derived class ParserDerived
. Error: error: no type named ‘DType’ in ‘struct ParserDerived<double>’ using DType = typename Parser::DType;
So somehow the types
I am afraid of using such patterns and because I might be realising in the future that all my construction with these patterns collapse into tons of hard to understand compiler failures...
What would be a better approach for the problem below?
CODE
#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;
}
回答1:
Here's what happens:
d
gets defined asParserDerived<double>
, so that is instantiated- The base class is given as
Parser<double, ParserDerived<double>>
, so that is instantiatedDType
gets defined asdouble
DerivedType
gets defined asParserDerived<double>
ModuleType
gets defined asModule<ParserDerived<double>>
ModuleOptions
gets defined asModule<ParserDerived<double>>::OptionsType
, soModule<ParserDerived<double>>
is instantiatedDType
gets defined asParserDerived<double>::DType
← ERROR HEREOptionsType
gets defined asint
Base
gets defined asParser<double, ParserDerived<double>>
ModuleType
gets defined asParser<double, ParserDerived<double>>::ModuleType
DType
gets defined asParser<double, ParserDerived<double>>::DType
- The base class is given as
If you draw out the instantiations like that, it becomes clear that DType
is used before it is defined. It's not immediately obvious that the template instantiation has to be performed sequentially like this, but dyp's comment on your question already answers that it's a valid means of template instantiation, and you can see that it's what multiple compilers do.
You will have to re-work your design. In this particular case, I think a very workable approach would be to mimic the standard library (a bit) and provide a parser traits class. You would move the definitions of ModuleType
and DType
there, so that accessing those definitions would not require instantiation of the parser class.
In response to your comment:
It shouldn't matter whether you comment the derived class's DType
since that cannot seen regardless of whether it's defined, but it's a good question why the base class's DType
doesn't get used in its place. Parser<double, ParserDerived<double>>
is getting instantiated in order to use it as a base class, but during that instantiation it isn't seen as the base class yet. After the instantiation has been performed, the compiler would first make sure that Parser<double, ParserDerived<double>>
is suitable as a base class, and only then would it become the base class.
For a shorter example that more clearly shows this:
template <class B> struct A {
static void f(A &);
static decltype(f(*(B*)0)) g();
};
struct B : A<B> { };
Since B
derives from A<B>
, A<B>::f(A<B> &)
should be callable when passed an lvalue of type B
. That does not, however, prevent the compiler from complaining about the declaration of g
, and clang's error message quite explicitly calls A<B>
and B
unrelated types:
error: non-const lvalue reference to type 'A<B>' cannot bind to a value of unrelated type 'B'
Here too this happens because B
only becomes known as deriving from A<B>
after the instantiation of A<B>
has completed.
回答2:
I came up with a simple and effective solution to circumvent the above typedef problems:
This is little more complex example, which uses a ParserTraits
struct which defines all types which are needed in Parser
and Modules ModuleA,ModuleB
. It is now also possible to use the defined ModuleB
ind the ModuleA
class and also the Parser
can access all Module#Options
typedefs...
The code is here: 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();
}
来源:https://stackoverflow.com/questions/24698598/how-to-avoid-simple-recursive-template-typedefs