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

Error when using template class as template template-parameter in member variable

本文关键字:参数 出错 成员 变量      更新时间:2023-10-16

我在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 标准不允许此设计架构,我可以使用哪种解决方法?

问题是在

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

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

根据@Igor的评论,我想出了这个问题的几种解决方法。基于这样一个事实,即在其声明中引用Bar是指Bar的这种专业化(引用@Igor)。

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

字段具有不完整的类型"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 的类似 SO 问题,但这里的重要区别在于我的代码不在 g++ 上编译,而他们的代码在 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实例,因此这是解决编译错误的非常好的方法。

附言我真的很想感谢@user5800314,因为他的解决方法确实有效,但是我在这里提供了不同的解释,并且由于我在这里提供的解释是我认为正确的,我认为我不能将@user5800314的答案标记为接受。