为什么模板类继承另一个模板类时,需要重新指定typedef并且函数调用要限定作用域?

9
当一个模板类继承自另一个模板类时,基类中的typedef必须重新定义(即它们不会自动继承),并且基类中的函数调用需要限定作用域。为什么?这难道不已经很明确了吗?
例如,如果我有20个模板类,所有模板类都定义相同的typedef,我无法引入一个包含这些定义的基类并从中继承,因为我仍然需要在每个类中重新定义typedef,这使源代码变得非常冗长。
我看到在这个question中已经讨论过这个问题了,但是我不理解评论。
``` C++的名称查找规则指定,仅当名称依赖于模板参数(如果它是“依赖名称”)时才在模板化的基类中搜索名称。如果名称不依赖于模板参数,则不会在那里搜索。 ```
这是为什么?这对我来说毫无意义。
也许,下面的代码片段能更好地阐明我的问题:
#include <iostream>

template <unsigned N, typename T>
struct A
{
   typedef T U;
   static void foo(T x) { std::cout << N + x << "\n"; }
};

template <unsigned N, typename T>
struct B : public A<N, T>
{
    // Why do I have to redeclare U? Isn't is unambiguous already?
    typedef typename A<N, T>::U U;

    //  why do I have to specify B::? Isn't it unambiguous already?
    static void zoo(U x) { B::foo(x); }  
};

int main()
{
    B<2,int>().zoo(3);
    B<2,double>().zoo(3.5);
    return 0;
}

我最近也遇到了这个问题,发现它非常奇怪。一个注意点是,你可以为基类创建一个typedef(我经常在模板基类中使用),而不是使用"typedef typename A<N, T>::U U;",然后使用using:typedef A<N, T> base_type; using typename base_type::U; - SoronelHaetir
谢谢,我知道这一点。缺点是你必须在所有地方使用 typename base_type::。表达式变得冗长,充斥着无用的关键字。 - Fabio
2个回答

3
这是由于双阶段查找。引用自此处的一句话:
  1. 模板定义时间:在实例化之前,编译器会解析模板并查找任何“非依赖”名称。如果名称查找的结果不依赖于任何模板参数,则该名称为“非依赖性名称”,因此从一个模板实例化到另一个模板实例化结果相同。
  2. 模板实例化时间:当模板被实例化时,编译器将查找任何“依赖性”名称,因为它具有执行查找所需的完整模板参数集。此查找的结果可以(并经常)因一个模板实例化而异。
因此,在初始解析期间,编译器认为U是非依赖名称,并尝试查找它,但它找不到任何内容,因为它不允许查找依赖基类。但是,如果U是依赖名称,则编译器将在实例化阶段查找它,并在基类中找到它。
顺便说一下:VS将很容易编译此代码,但是他们最近已经添加了双阶段查找的可能性。

谢谢Yola。你的回答解释了效果,但没有解释原因。为什么要使用“两阶段查找”?这是标准规定的吗?显然它有很多缺点。有没有任何优势?或者有没有任何神秘的用例,明显的做法不是正确的选择? - Fabio
@Fabio,你可以查看我回答中提到的文章,它提供了一个例子,其中使用和不使用两阶段查找会得到不同的结果。我认为在这个例子的逻辑之后,为什么引入这个特性将更加清晰。 - Yola
@Fabio 这里提供了更多示例的非常详细的解释:[https://blogs.msdn.microsoft.com/vcblog/2017/09/11/two-phase-name-lookup-support-comes-to-msvc/] - Yola

1

根本原因在于类可以进行专业化:

template<class T>
struct A {};

template<class T>
struct B : A<T> {
  typedef typename A<T>::referent target;
  void niceName() {A<T>::uglyName();}
};

template<class T>
struct A<T*> {
  typedef T referent;
  void uglyName();
};

B<int*>::target i;  // OK: int

当然,反向情况(即主模板定义有用的内容,我们只是担心特化可能会更改或删除它们)更为常见,这使得规则似乎是武断的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接