【问题标题】:Dependent scope and nested templates依赖范围和嵌套模板
【发布时间】:2011-09-28 02:54:11
【问题描述】:

当我编译这个时:

#ifndef BTREE_H
#define BTREE_H
#include <QList>

template <class T, int degree>
class btree
{
public:
    class node
    {
    public :
        node();
    private:
        node* parent;
        QList<T> values;
        QList<node*> children;
    };
public:
    btree();
    void insert(const T& value);
    node* findLeaf(const T& value);
    void performInsertion(const T& value, node& place);
    //
    node* root;
};
#endif // BTREE_H

findLeaf 的实现是这样的:

template <class T, int degree>
btree<T,degree>::node* btree<T,degree>::findLeaf(const T &value)
{
    if(root == NULL)
        return root;
}

出现此错误:

 error: need ‘typename’ before ‘btree<T, degree>::Node’
 because ‘btree<T, degree>’ is a dependent scope

【问题讨论】:

  • 如果不小心:错误可能发生在findLeaf的定义处。
  • 我正在尝试粘贴我的代码,但没有显示出来!
  • 我希望 SO 将行号放在代码旁边,以便于引用。

标签: c++ templates nested-class


【解决方案1】:

不,这与 C++ 的语法无关,而是与 C++ 模板的惰性实例化有关 和两阶段查找。


在 C++ 中,从属名称是一个名称或符号,其含义取决于一个或多个 模板参数:

template <typename T>
struct Foo {
    Foo () {
        const int x = 42;
        T::Frob (x);
    }
};

通过单独解析 sn-p,在不知道 T 的所有未来值的情况下,没有 C++ 编译器可以推断出 T 中的 frob 是函数名、类型名还是其他东西,或者它是否存在.

举个例子说明为什么这是相关的,想象一下你将用一些类型代替 T:

struct Vietnam {
    typedef bool Frob; // Frob is the name of a type alias
};    

struct Football {
    void Frob (int) {} // Frob is a function name
};

struct BigVoid {};     // no Frob at all!

将它们放入我们的 Foo 模板中:

int main () {
    Foo<Vietnam> fv;   // Foo::Foo would declare a type
    Foo<Football> ff;  // Foo::Foo would make a function call
    Foo<BigVoid> fbv;  // Foo::Foo is not defined at all
}

与此相关的是两阶段查找的概念。在第一阶段, 解析编译非依赖代码:

template <typename T>
struct Foo {
    Foo () {
        const int x = 42; // does not depend on T
        T::Frob (x);      // full check skipped for second phase, only rudimentary checking
    }
};

第一阶段是让编译器在模板定义本身中发出错误消息的原因。

第二阶段将触发模板错误以及当时已知的类型 T。

一些早期的 C++ 编译器只会在您实例化模板后解析它们;使用这些编译器,不需要消除歧义,因为在实例化时,模板参数是已知的。这种单阶段查找的问题是模板本身中的许多错误根本不会被检测到,或者只是在编译的后期才被检测到,因为模板默认是惰性实例化的,即只有一部分扩展了实际使用的类模板,此外,它还为您提供了更多可能源自模板参数的神秘错误消息。

因此,为了使两阶段查找起作用,您必须帮助编译器。 在这种情况下,您必须使用typename 来告诉编译器您的意思是一个类型:

template <typename T>
struct Foo {
    Foo () {
        const int x = 42;
        typename T::Frob (x);
    }
};

编译器现在知道 x 是 Frob 类型的变量 :)

【讨论】:

  • 很好的答案。非常感谢。
  • 现在我明白错误信息背后的原因是什么了,谢谢。
  • 很好的解释!希望所有关于 SO 的答案都是这样的答案!
  • 我想知道为什么这不是提出问题的用户选择的答案。
【解决方案2】:

C++ 语法太可怕了,因此,当给定一个模板类时,不可能知道您所指的 ::node 是变量/常量还是类型。

因此,该标准要求您在类型之前使用 typename 以消除这种歧义,并将所有其他用法视为变量。

这样

template <typename T, int degree>
typename btree<T,degree>::node* btree<T,degree>::findLead(T const& value)
^~~~~~~~

是定义的正确签名。

【讨论】:

  • 现在编译器找不到我的函数:`no 'btree::node* btree::findLead(const T&)' 类中声明的成员函数' btree''
  • 对不起,这可以正常工作。我输入了一个错误的字符...谢谢
  • @phresnel:我发现这个关键字在很多情况下是多余的,编译器在其错误消息中提供的有用的事实本身就是一个红鲱鱼。函数签名包含值是没有意义的,无论是在返回类型中还是作为参数类型。然而你不得不写typename。我承认可能存在需要typename 的复杂情况(由于可怕的语法),但大多数情况下并非如此。 “可怕”的例子:template &lt;typename T, size_t N&gt; T* begin(T (&amp;array)[N]) { return array; }(避免指向函数的指针)
  • ... 这很难理解,实际上会胜过大多数读者。为什么不将数组声明为 T[N]&amp; array ?或指向函数的指针 typedef void (*)() func_ptr; ?不,取而代之的是,我们有一个从 C 继承的混搭,并且在混合中加入模板使得即使对于编译器也非常难以解析,所以我们这些可怜的人......
  • @Matthieu M.:我不同意语法在几个方面都很差。但是, typename 的事情并不植根于此。在 C++ 中,函数签名始终可以定义和覆盖参数列表中的默认参数。在元编程中,函数签名可以并且通常基于值,例如enable_if&lt;is_foo&lt;T&gt;::value&gt; bar()。忽略其他一些方面,我认为在模板参数列表中只需要类型名消歧,而不是在外部,这会比必要的更令人困惑。 ...
猜你喜欢
  • 2011-03-19
  • 1970-01-01
  • 1970-01-01
  • 2017-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-13
  • 2021-09-14
相关资源
最近更新 更多