【问题标题】:Error when using template class as template template-parameter in member variable在成员变量中使用模板类作为模板模板参数时出错
【发布时间】:2016-01-17 02:02:25
【问题描述】:

我在我的 C++ 项目中使用模板,但在使用模板类型作为模板模板参数时遇到了问题。我认为描述它的最好方法是举一个产生错误的例子:

template <template<class> class P, typename T>
class Foo {
    P<T> baz;
};

template <class T>
class Bar {
    Foo<Bar, T> memberFoo;

    void makeFoo() {
        Foo<Bar, T>* f = new Foo<Bar, T>();
    }
};

Foo<Bar, int> globalFoo;

globalFoo 的声明不会导致错误,但memberFoof 的声明会导致编译器错误:

错误:模板模板参数的模板参数必须是类模板 或输入别名模板

只有在 Bar 类的声明中使用 Bar 作为模板参数时才会发生该错误,但同时使用 clang 和 g++ 都会发生该错误。这似乎会记录在某处,但谷歌搜索不会产生 SO 问题或其他文档。

在 C++ 中这种使用模板是不合法的,还是我对如何定义和使用模板有误解?如果 C++11 标准不允许这种设计架构,我可以使用什么解决方法?

【问题讨论】:

  • 我认为问题在于,在Bar 的定义中,纯名称Bar 指的是this 专业化——即Bar&lt;T&gt;——而不是模板作为一个整体。一种解决方法:在Bar 定义的顶部添加template &lt;typename U&gt; using Bar2 = Bar&lt;U&gt;;,并在需要引用非专业模板的地方使用Bar2 代替Bar
  • 这似乎是递归的。 Bar 有一个 Foo 成员,但是 Foo 是模板化的,因此它具有与它所属的类完全相同的 Bar 成员。所以 Bar 包含自己,这是不允许的。我认为,特定错误是由于在定义期间尝试使用 Bar 作为模板模板参数而没有前向声明引起的。

标签: c++ templates c++11


【解决方案1】:

问题是在实例化时产生了不完整的类型。 Clang 和 G++ 产生不同的错误,因为 Clang(显然)没有实现 C++11 规则,即注入的类名在用作模板模板参数时可以引用类模板本身(顺便说一句,Igor 的建议不起作用。)将Bar 更改为::Bar 可修复此错误,这使 Clang 像 G++ 一样指出不完整的错误。将baz 更改为P&lt;T&gt;* 允许它编译。

注意即使它编译,它也可能是未定义的行为。我建议重新设计你的课程。

【讨论】:

    【解决方案2】:

    根据@Igor 的评论,我已经为这个问题找到了一些解决方法。是基于以下事实:引用 Bar 在其声明中指的是 Bar 的这种特化(引用@Igor)。

    请注意,所有这些变通方法都依赖于 baz 可以声明为指针这一事实。 baz 不是指针会导致 @Nir 在 cmets 中提到的递归问题并导致错误:

    字段的类型不完整 'Foo, int>'

    解决方法 1

    添加Bar 的前向声明并创建模板别名Bar2。要编译,baz 必须是一个指针:

    template <template <typename> class P, typename T>
    class Foo {
        P<T>* baz;
    };
    
    template<typename T> class Bar;
    template <typename U> using Bar2 = Bar<U>;
    
    template <class T>
    class Bar {
        Foo<Bar2, T> memberFoo;
    
        void makeFoo() {
            Foo<Bar2, T>* f = new Foo<Bar2, T>();
        }
    };
    
    Foo<Bar, int> globalFoo;
    

    模板别名的前向声明和使用会强制编译器在定义memberFoof 时使用非专门版本的Bar,而在globalFoo 的定义中默认使用非专门版本。

    解决方法 2

    此解决方法基于@user5800314 的回答。我觉得没有必要重新说明他的解决方法,但我确实觉得值得注意的是它起作用的原因。

    我已经阅读了关于注入类名和 C++11 的 similar SO question,但这里的重要区别是我的代码不能在 g++ 上编译,而他们的代码可以。我不认为这个问题是缺乏注入类名的实现。我相信这个变通方法修复了编译错误,因为使用::Bar 而不是Bar 再次强制编译器访问Bar 的全局(非专业)版本而不是访问Bar 的本地(专业)版本。

    解决方法 3

    Foo 指定为具有类(或类型名)模板参数而不是模板模板参数,并明确说明在使用Foo 模板时使用的特化。这也要求baz 是一个指针,并且它不使用模板类型:

    template <class P, typename T>
    class Foo {
        P* baz;
    };
    
    template <class T>
    class Bar {
        Foo<Bar, T> memberFoo;
    
        void makeFoo() {
            Foo<Bar, T>* f = new Foo<Bar, T>();
        }
    };
    
    Foo<Bar<int>, int> globalFoo;
    

    此解决方法通过要求为Foo 模板提供特定类来解决模板模板参数的潜在混淆。这种解决方法在某些情况下可能不可用,但在其他情况下可能是一个优雅的解决方案。例如,在我的例子中,我不需要从Bar 之外实例化Foo 的实例,所以这是解决编译错误的好方法。

    P.S.我真的很想感谢@user5800314,因为他的解决方法确实工作,但是我在这里提供了不同的解释,因为我在这里提供的解释是我相信是正确的,我觉得我不能将@user5800314 的答案标记为已接受。

    【讨论】:

    • 您发布的一些解决方法确实很奇怪。我认为这是一种 X-Y 情况,您最好尝试告诉人们您正在尝试做什么。我的意思是,您是否正在尝试编写递归数据结构?数据结构具有指向其类的其他实例的指针是不寻常的,而且很少是一个好主意(在 C++ 中)。
    • 我只是分享我发现的让这个类结构工作的东西。我在各个模块之间传递数据(这里由Bar 类表示。他们必须知道它们连接到哪些其他模块,并且连接由Foo 类描述。有没有更好的方法来描述模块以及他们使用 C++ 的连接?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多