根据提案 P0634-Down with typename,C++20 之后 typename 在有些地方不再必要。

原文主要内容如下:

If X::Y — where T is a template parameter — is to denote a type, it must be preceded by the keyword typename; otherwise, it is assumed to denote a name producing an expression. There are currently two notable exceptions to this rule: base-specifiers and mem-initializer-ids. For example:

template
struct D: T::B { // No typename required here.
};

Clearly, no typename is needed for this base-specifier because nothing but a type is possible in that context.

简言之,以前若需要使用模板中的嵌套类型,必须在前面加上 typename 以表示这是一个类型。而编译器供应商很快意识到在许多语境下其实他们知道指定的是否是类型,于是提议将 typename 变为可选项,只在编译器无法确定的语境下 typename 才是必选项。

这项提议内容最终加入了 C++20,因此,C++20 编写模板代码将会进一步简化。举个例子,C++20 可以这样写代码:

struct S {
    using X = int;
    using R = char;
};

// class template
template<class T,
         auto F = typename T::X{}>    // typename required
struct Foo {
    using X2 = T::X;                  // typename not required
    typedef T::X X3;                  // typename not required
    T::X val;                         // typename not required

    T::R f(T::X x) {                  // typename not required
        typename T::X i;              // typename required
        return static_cast<T::R>(x);  // typename not required
    }

    auto g() -> T::R {                // typename not required
    }

    template<class U = T::X>          // typename not required
    void k(U);
};

// function template
template<class T>
T::R                                  // typename not required
f(typename T::X x)                    // typename required
{
    using X2 = T::X;                  // typename not required
    typedef typename T::X X3;         // typename required
    typename T::X val;                // typename required

    typename T::R f();                // typename required
    auto g() -> T::R;                 // typename not required
    void k(typename T::X);            // typename required

    auto lam = [](T::X) {};           // typename not required

    return static_cast<T::R>(x);      // typename not required
}

int main() {
    Foo<S> foo;
    f<S>(65);
}

示例基本列举了大多数使用 typename 的情境,其中许多情境下 typename 已不再需要。

这里解释一点,为何在类模板与函数模板中,有些情境相同,但是 typename 使用规则却并不相同呢?回答这个问题,再次引用一下 Understanding variadic templates,在这篇文章中我抛出了一个观点:函数模板处理的是数值,类模板处理的是类型。在「类作用域」和「函数作用域」下编译器对于嵌套类型的解析形式是不一样的,前者解析为类型,后者解析为非类型。因此,在函数模板里许多地方依旧需要使用 typename,而在类模板里则不必要。总之就是,仍旧需要使用 typename 的地方都可能存在歧义,所以需要借助 typename 来标识其为类型。

此外,这里还有一个有趣的点。Modern C++ 中建议使用 using 代替 typedef 来定义类型别名,以前二者除了写法似乎并不区别。但是现在,使用 using 定义嵌套类型的别名时不用指定 typename,而 typedef 仍然需要。因此,using 代替 typedef 绝对是一种比较好的做法。

Leave a Reply

Your email address will not be published. Required fields are marked *

You can use the Markdown in the comment form.