当模板参数包含非静态成员的名称时,是否需要'typename'?

时间:2013-07-14 19:00:32

标签: c++ templates language-lawyer typename

语言律师的另一个。

对于以下代码,无论是否存在A<int>的显式实例化:

clang 3.0在typenameType1的声明中需要Type3,而Type2则不需要。{/ p>

gcc 4.8.1在typename的声明中需要Type1,而Type2Type3则不需要。{/ p>

struct C
{
    int f1();
    static int f2();
    int m;
};

template<int i>
struct S
{
    typedef int Type;
};

template<typename T>
struct B : C
{
};

template<typename T>
struct A : B<T>
{
    void f()
    {
        typedef typename S<sizeof(C::f1())>::Type Type1; // typename required
        typedef S<sizeof(C::f2())>::Type Type2; // typename not required
        typedef typename S<sizeof(C::m)>::Type Type3; // typename not required?
    }
};

template struct A<int>;

我在typename的声明中需要Type1,因为对非静态成员的调用的隐含对象参数是(*this),其类型是依赖的。 见[over.call.func]:

  

在非限定函数调用中,该名称不受->.运算符限定,并且具有更通用的primary-expression形式。在函数调用的上下文中查找名称,遵循函数调用中的名称查找的常规规则。该查找找到的函数声明构成候选函数集。由于名称查找的规则,候选函数集包括(1)完全由非成员函数或(2)完全由某些类T的成员函数。在情况(1)中,参数列表与呼叫中的表达式列表。在情况(2)中,参数列表是通过在限定函数调用中添加隐含对象参数而增强的调用中的表达式列表。如果关键字this在范围内并引用类T或派生类T,则隐含的对象参数为   (*this)

遵循此逻辑,typename的声明中不需要Type2,因为该成员是静态的。 typename的声明中也不需要Type3因为表达式不是对非静态成员的调用。

我读过这篇文章:Where and why do I have to put the "template" and "typename" keywords? ..并通过[temp.dep.type]和[temp.dep.expr]中的规则。在确定表达式是否依赖时,我看不到任何指定是否应该对非静态成员函数名称进行特殊处理的内容。标准是否指定了这个?

编辑:删除了基于[class.mfct.non-static]转换为类成员访问表达式的讨论 - 凯西的回答更详细地讨论了这一点。

2 个答案:

答案 0 :(得分:3)

C是通过非限定名称查找在阶段1查找期间解析的非依赖名称。 C::f1C::f2是在模板查找的第一阶段通过限定名称查找解析的非依赖名称。无论C是否为A的基础,都是如此。

问题中引用的[class.mfct.non-static]文本似乎来自C ++ 03。 C ++ 11文本(9.3.1 / 3)读作:

  

id-expression (5.1)不属于类成员访问语法(5.2.5)并且不用于形成指向成员(5.3.1)的指针时,用于如果名称查找(3.4)将 id-expression 中的名称解析为a X,则可以使用this的上下文中的C成员某些类C的非静态非类型成员,如果 id-expression 可能被评估 [emph。我的或XX(*this)的基类, id-expression 被转换为类成员访问表达式(5.2.5)使用.(9.3.2)作为C::f1运算符左侧的后缀表达式

this在可以使用C::f1的上下文中进行潜在评估,因此(*this).C::f1会转换为B<T>。由于(*this).C::f1明确依赖于模板参数,因此(*this).C::f1是每个14.6.2.1/5的未知专业化 成员。 [该标准使用这个术语“未知专业化的成员”来表示可以指示某些模板专业化的成员的表达式,但所有 专业化。 ]

由于(*this).C::f1()表示未知专业化的成员,因此它是每个14.6.2.2/5的类型相关的类成员访问表达式。根据扩展,sizeof((*this).C::f1())依赖于类型14.6.2.2/1。然后,S<sizeof((*this).C::f1())>按照14.6.2.3/2 取决于值S<sizeof((*this).C::f1())> - 具有依赖于值的参数表达式的 simple-template-id - 然后是每14.6.2.1/8的依赖类型。

由于S<sizeof((*this).C::f1())>::Type是依赖的,

typename

依赖于每个14.6.2 / 1“...如果 id-expression unqualified-id template-id 其中任何模板参数都取决于模板参数。“最后 - 但并非最不重要的是,14.6 / 2要求使用关键字typename S<sizeof((*this).C::f1())>::Type来表示从属名称是指类型:

C::f1

认为这就是完整的推理链。

编辑:这个答案似乎是错误的。短语“潜在评估”在3.2 / 2一个定义规则[basic.def.odr]中定义:

  

表达式可能被评估,除非它是未评估的操作数(第5条)或其子表达式。

示例中的

C::f1()肯定是sizeof的子表达式,它是{5.3}每个5.3.3 / 1 Sizeof [expr.sizeof]的qualified-id()未评估的操作数。

编辑:我认为表达式this目前不是,但应该被认为是类型相关的(1)它出现在类模板的成员函数中,(2) qualified-id < / em>是一个非依赖名称,表示某些类的非静态成员函数的重载集,以及(3)this的类型是依赖的。确定表达式的类型需要每个13.3.1.1.1 / 3的重载解析,如果this在范围内而不确定T引用的类的类型,则不可能。

我认为这可以通过向14.6.2.1/5 [temp.dep.type]添加一个子弹来实现:

  
      
  • 在非限定函数调用(13.3.1.1.1 / 3 [over.call.func])中使用的 qualified-id ,表示某个类{{1}的一组成员函数当前实例化至少有一个依赖基类时,它不是当前实例化或非依赖基类。
  •   

答案 1 :(得分:2)

我不完全确定,但这是一个使用你的引用的解释:

  • C::f2只是一个普通的函数,因此无条件地知道,sizeof(C::f2())不依赖于模板参数T - 它只是一个简单的类型,如S<6>

  • C::f1()实际上是this->f1(),由于this取决于模板参数TS<sizeof(this->f1())>是依赖类型,因此需要消歧。