如何避免简单的递归模板typedef

时间:2014-07-11 13:11:34

标签: c++ templates c++11 metaprogramming

我有以下简单问题: 课程template<typename D> ParserModuleType定义为Module<Parser>。我想将解析器类型注入到模块中,以便能够从解析器中再次提取几种类型。这很方便,因为在Module中只需要一个模板参数。但是如果解析器需要模块中定义的某些类型(例如OptionsType),那么问题就出现了,使用声明Parserusing ModuleOptions = ...中访问它显然不适用于派生类ParserDerived。错误:error: no type named ‘DType’ in ‘struct ParserDerived<double>’ using DType = typename Parser::DType;所以不知何故类型

我害怕使用这样的模式,因为我将来可能会意识到所有使用这些模式的构建都会陷入大量难以理解的编译器故障......

对于下面的问题,有什么更好的方法?

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;
}

2 个答案:

答案 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对您的问题的评论已经回答它是模板实例化的有效方法,您可以看到它是多个编译器所做的事情。

您必须重新设计您的设计。在这种特殊情况下,我认为一种非常可行的方法是模仿标准库(一点点)并提供一个解析器特征类。您可以在那里移动ModuleTypeDType的定义,以便访问这些定义不需要实例化解析器类。

回应你的评论:

您是否对派生类的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();
}