如何避免简单的递归模板类型定义

How to avoid simple recursive template typedefs

本文关键字:类型 定义 递归 何避免 简单      更新时间:2023-10-16

我有以下简单的问题:一类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类。您可以将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>,当传递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();
}