在成员变量中使用模板类作为模板模板参数时出错

Posted

技术标签:

【中文标题】在成员变量中使用模板类作为模板模板参数时出错【英文标题】: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 作为模板模板参数而没有前向声明引起的。 【参考方案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++ 的连接?

以上是关于在成员变量中使用模板类作为模板模板参数时出错的主要内容,如果未能解决你的问题,请参考以下文章

零值初始化&字符串常数作为函数模板参数

如何使用模板声明成员函数? (不是模板类)

每个可变参数模板参数生成一个类成员

使用在内部结构定义中保存函数的类成员变量,这些函数将用作 unordered_map 对象的模板参数

在 MSVC 中使用数据成员指针作为模板参数

如何检测类中是不是存在特定的成员变量?