嵌套类中的模板别名可见性

12

请考虑以下内容:

template<typename X>
struct Z {};

struct A
{
    using Z = ::Z<int>;

    struct B : Z
    {
        using C = Z;
    };
};

这个编译没有问题。很好。但现在在Z中添加另一个参数:

template<typename X, typename Y>
struct Z {};

struct A
{
    template<typename X>
    using Z = ::Z<X, int>;

    struct B : Z<B>
    {
        using C = Z<B>;  // error: too few template arguments for class template 'Z'
    };
};

好的,也许在派生嵌套类B时,在类A中定义模板别名Z是可见的,但不在其内部,因为全局定义的Z有两个参数,这会触发错误。

但是,当Z只是A中的类型别名时,为什么行为不同呢?

最后,将A变为一个模板:

template<typename X, typename Y>
struct Z {};

template<typename T>
struct A
{
    template<typename X>
    using Z = ::Z<X, int>;

    struct B : Z<B>
    {
        using C = Z<B>;
    };
};

现在错误已经消失了。 为什么?

(在Clang 3.6和GCC 4.9.2上测试过)

1个回答

9
简而言之:从Z的特化派生引入了::Z注入类名,该类名在别名模板之前找到。然而,如果A是一个模板,则由于B的基类是相关的,注入类名将不再被找到。
考虑[temp.local]/1:
与普通(非模板)类一样,类模板具有注入类名(第9条款)。注入类名可用作模板名或类型名
并且[basic.lookup]/3:
对于名称[...]查找,类的注入类名(第9条款)也被视为该类的成员Z作为未限定名称进行查找;[basic.lookup.unqual]/7:

enter image description here

因此,Z作为注入类名,被视为Z<B, int>的成员,并被用作模板名称,这使得你的第二个程序无法通过编译。实际上,你的第一个片段也使用了注入类名 - 下面的代码片段也无法编译:
struct A
{
    using Z = ::Z<float>;
    struct B : ::Z<int>
    {
        static_assert( std::is_same<Z, ::Z<float>>{}, "" );
    };
};

最后,如果将A作为模板,则根据[temp.dep.type]/(9.3)1B是一个相关类型,因此根据[temp.dep.type]/(9.7),Z<B>是一个相关类型。 因此,在查找未限定-id Z时,根据[temp.dep]/3,不会检查基类Z<B>

在类的定义中[..],一个相关基类(14.6.2.1)的作用域在类模板或成员的定义点以及类模板或成员的实例化期间都不会在未限定名称查找期间被检查。

因此,无法找到注入类名


1 B是当前实例的“依赖成员”的“嵌套类[...]”(强调我的),因为

如果一个名称是当前实例的成员,并且在查找时引用了当前实例的至少一个成员所属的类,则该名称是当前实例的“依赖成员”。


哇,这很清楚,谢谢。错误实际上是当 A 不再是一个模板时出现的,我原以为这会简化代码。然而,我现在被迫为两个 Z 使用两个不同的名称,这只会让代码更丑陋。如果有任何更好的解决方法,请告诉我。 - iavr
@iavr using C = Z;怎么样?(对于模板参数A不起作用) - Columbo
现在这真是令人印象深刻 :-) 是的,它在这个简化的代码上运行正常,但在我的原始代码上却不行(未知类型名 'Z')。我得检查一下差异出在哪里。A不再是一个模板,而且我打算保持这样。 - iavr
啊,原来是因为我将外部的 Z 重命名了(比如改成了 Z1)。现在它正常工作了,并且真的简化了,因为我根本不需要 using C ...。太棒了,再次感谢! - iavr

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