【问题标题】:How to avoid simple recursive template typedefs如何避免简单的递归模板类型定义
【发布时间】:2014-09-10 10:54:09
【问题描述】:

我有以下简单的问题: 一个类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; 所以不知何故类型

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

以下问题的更好方法是什么?

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

【问题讨论】:

  • 您能否将Parser 中的typedef 独立于ModuleTypeA 移动到嵌套类型中并将其传递给ModuleA
  • 我不明白。如果取消注释标记为“取消注释这个!!”的行,您的代码应该会导致编译器错误,对吗?但事实并非如此。 GCC、clang、Intel 都同意代码没问题。
  • 现在我也是一头雾水,在出现错误之前,damm,尝试重现错误
  • 有关是否允许这样做的讨论,请参阅stackoverflow.com/q/17478621
  • 其实代码,可以,但是hvd的评论还不错!我认为我的问题出在其他地方

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


【解决方案1】:

会发生什么:

  • d 被定义为 ParserDerived&lt;double&gt;,因此被实例化
    • 基类被指定为Parser&lt;double, ParserDerived&lt;double&gt;&gt;,所以它被实例化了
      • DType 被定义为 double
      • DerivedType 被定义为 ParserDerived&lt;double&gt;
      • ModuleType 被定义为 Module&lt;ParserDerived&lt;double&gt;&gt;
      • ModuleOptions 被定义为 Module&lt;ParserDerived&lt;double&gt;&gt;::OptionsType,所以 Module&lt;ParserDerived&lt;double&gt;&gt; 被实例化
        • DType 被定义为 ParserDerived&lt;double&gt;::DType此处出错
        • OptionsType 被定义为 int
    • Base 被定义为 Parser&lt;double, ParserDerived&lt;double&gt;&gt;
    • ModuleType 被定义为 Parser&lt;double, ParserDerived&lt;double&gt;&gt;::ModuleType
    • DType 被定义为 Parser&lt;double, ParserDerived&lt;double&gt;&gt;::DType

如果你像这样画出实例,很明显DType 在定义之前就被使用了。模板实例化必须像这样按顺序执行并不是很明显,但是 dyp 对您的问题的评论已经回答了它是模板实例化的有效方法,您可以看到它是多个编译器所做的。

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

回应您的评论:

您是否评论派生类的DType 无关紧要,因为无论它是否已定义都无法看到,但这是一个很好的问题,为什么基类的DType 没有被使用。 Parser&lt;double, ParserDerived&lt;double&gt;&gt; 正在实例化以将其用作基类,但在该实例化期间,它还没有被视为基类。实例化完成后,编译器会首先确定Parser&lt;double, ParserDerived&lt;double&gt;&gt;适合作为基类,然后才会成为基类。

更短的例子更清楚地表明了这一点:

template <class B> struct A {
  static void f(A &);
  static decltype(f(*(B*)0)) g();
};
struct B : A<B> { };

由于B 派生自A&lt;B&gt;A&lt;B&gt;::f(A&lt;B&gt; &amp;) 在传递B 类型的左值时应该是可调用的。然而,这并不能阻止编译器抱怨g 的声明,并且clang 的错误消息非常明确地调用了A&lt;B&gt;B 不相关的类型:

错误:对类型“A”的非 const 左值引用无法绑定到不相关类型“B”的值

这里也会发生这种情况,因为 B 仅在 A&lt;B&gt; 的实例化完成之后才被称为派生自 A&lt;B&gt;

【讨论】:

  • 感谢您的澄清!!嗯。实际上我们可以在 ParserDerived 类中取消注释 using DType 并且错误保持不变。这有意义吗?我认为它应该在基类中看到 typedef DType 但这不是发生的事情......
  • @Gabriel 好问题,我在回答中对此进行了扩展。
  • @Gabriel 几乎:template &lt;typename Parser&gt; struct ParserTraits /* intentionally undefined */ ; -- 然后让每个解析器类提供一个专门化(并确保专门化不需要完成解析器类)。
  • @Gabriel 请注意,事后看来,一个更易于使用的替代方案是提供该特征类作为模板参数,而不是(或什至可能是除了)派生类。
  • @Gabriel 我看不出有什么问题。根据您的具体需求,它甚至可能比您需要的更通用(我的方法也可以——你会对此做出更好的判断——在这种情况下,它可以被简化) ,但它应该做你想做的事,而且它应该可靠地做到这一点,据我所知。
【解决方案2】:

我想出了一个简单有效的解决方案来规避上述 typedef 问题: 这是一个稍微复杂一点的例子,它使用了一个ParserTraits 结构,它定义了Parser 和模块ModuleA,ModuleB 中需要的所有类型。现在还可以使用已定义的 ModuleBModuleA 类,并且 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();
}

【讨论】:

    猜你喜欢
    • 2020-11-10
    • 1970-01-01
    • 2016-09-23
    • 1970-01-01
    • 2012-10-26
    • 2014-11-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多