【问题标题】:Is the name of a non-static-member dependent when used within a non-static member function?在非静态成员函数中使用时,非静态成员的名称是否依赖?
【发布时间】:2015-02-27 14:47:14
【问题描述】:

gcc 5.0 和 clang 3.6 在以下示例中都需要 typename 关键字:

template<int n>
struct I
{
    typedef int Type;
};

template<typename T>
struct A
{
    int m;

    void f()
    {
        typedef typename I<sizeof m>::Type Type; // typename required
    }
};

C++11 标准中的以下措辞涵盖了这一点:

[temp.dep.type]/8

一个类型是依赖的,如果它是

  • 一个简单模板ID,其中模板名称是模板参数或任何模板 arguments 是依赖类型或依赖于类型或值的表达式

如果sizeof m 是值相关的,那么I&lt;sizeof m&gt; 是相关的。

[temp.dep.expr]/4

以下形式的表达式从不依赖于类型(因为表达式的类型不能 依赖):

sizeof unary-expression

[temp.dep.constexpr]/2

如果一元表达式或表达式是类型相关的,则以下形式的表达式是值相关的 或者 type-id 是依赖的:

sizeof unary-expression

所以sizeof m 仅在 m 依赖时才依赖。

[expr.prim.general]/8

在 非静态成员函数的定义,命名非静态成员的标识符被转换为 类成员访问表达式

所以m 是类成员访问表达式中的成员。

[temp.dep.type]/4

一个名字是一个当前实例化的成员如果它是

  • 一个 id 表达式,表示类成员访问表达式 (5.2.5) 中的成员,其类型 对象表达式的当前实例化和 id 表达式,当查找时(3.4.5), 指当前实例化或其非依赖基类的至少一个成员。

所以看来m 是当前实例化的成员。

[temp.dep.type]/5

如果名字是未知专业化的成员,则它是

  • 一个 id 表达式,表示类成员访问表达式 (5.2.5) 中的成员,其中任一

    • 对象表达式的类型是当前实例化,当前实例化至少有 一个依赖基类,并且 id-expression 的名称查找未找到 当前实例化或其非依赖基类;或

    • 对象表达式的类型是依赖的,不是当前的实例化。

所以m 不是未知特化的成员 - 通过名称查找会发现它是当前实例化的成员。

[temp.dep.expr]/3

如果一个 id 表达式包含,则它是类型相关的

  • 通过名称查找关联的标识符与一个或多个声明为依赖类型的声明,
  • 嵌套名称说明符或限定 ID,用于命名未知专业化的成员

由于m 属于int 类型并且不是未知专业化的成员,因此这些项目符号都不会使id 表达式m 依赖。

[temp.dep.expr]/5

类成员访问表达式 (5.2.5) 如果表达式引用当前的成员,则依赖于类型。 实例化和被引用成员的类型依赖,或者类成员访问表达式 指代未知专业的成员。

m 转换为类成员访问表达式时,它仍然不依赖,因为它不引用未知特化的成员。

应该将m 视为依赖项吗?在相关的说明中,this-&gt;m 是否应该被视为依赖? std::declval&lt;A&gt;().m 呢?

编辑

最后,&amp;A::m 应该依赖吗?

【问题讨论】:

  • 我怎么知道你会问这个问题? :) 我认为从您之前的问题中可以清楚地看出标准措辞不当,或者编译器没有正确实施标准。您可能会找到更多示例,其中根据标准应该不依赖的类型或表达式被编译器视为依赖。
  • 您的问题似乎是:“我已经得出结论,sizeof m 不依赖,但编译器似乎认为这是依赖。我的证明有缺陷吗?”答案是:“不,我看不到任何缺陷。”相同的参数应该适用于this-&gt;mstd::declval&lt;A&gt;().m,因为在任何情况下对象表达式都引用当前实例化,而m 没有依赖类型。
  • 我认为这些问题的答案将来可能对其他人有用 - 诚然只对那些正在实施 C++ 的人。
  • std::declval&lt;A&gt;().m 很复杂,因为类成员访问的左侧是类型相关的,因此无法(?)确定它是否引用当前实例化
  • m翻译成的类成员访问表达式不就是this-&gt;m吗?所以如果这是有问题的,那么简单的m

标签: c++ templates c++11 language-lawyer dependent-type


【解决方案1】:

答案取决于是否可以查找m 以确定它是当前实例化的成员。 id-expression m 被转换为类成员访问(*this).m,这意味着在类成员访问中的限定名称查找规则是适用的。

通常,类型相关表达式的类型无法确定。是否应该对(*this).this-&gt; 进行例外处理尚不完全清楚。包含this 的表达式与类型相关,但(*this).this-&gt; 都明确命名当前实例化。

m

表达式m 确实与类型无关,因为它引用了当前实例化的成员。

在非静态成员的上下文中,m 被转换为类成员访问表达式(*this).m

[class.mfct.non-static]/3

当在类X 的成员中使用不属于类成员访问语法 (5.2.5) 且未用于形成指向成员 (5.3.1) 的指针的 id 表达式 (5.1) 时在可以使用它的上下文中 (5.1.1),如果名称查找 (3.4) 将 id-expression 中的名称解析为某个类 C 的非静态非类型成员,并且如果 id-表达式可能被评估或CXX 的基类,id-expression 使用(*this) 转换为类成员访问表达式(5.2.5)

发生转换是因为 m 是类 A 的成员,在类 A 的非静态成员中使用。

[expr.prim.general]/3

如果声明声明了一个类的成员函数或成员函数模板X, the expressionthis` 是可选的 cv-qualifer-seq 和结尾之间的“指向 cv-qualifier-seq X”类型的纯右值 函数定义、成员声明符或声明符。它不应出现在可选的 cv-qualifier-seq 之前 并且它不应出现在静态成员函数的声明中(尽管它的类型和值 类别在静态成员函数中定义,就像它们在非静态成员函数中一样)。

[expr.prim.general]/5

表达式this 不得出现在任何其他上下文中。 [ 例子:

class Outer {
    int a[sizeof(*this)]; // error: not inside a member function
    unsigned int sz = sizeof(*this); // OK: in brace-or-equal-initializer

    void f() {
        int b[sizeof(*this)]; // OK

        struct Inner {
            int c[sizeof(*this)]; // error: not inside a member function of Inner
        };
    }
};

——结束示例]

上述示例明确允许在非静态成员的sizeof 表达式中使用this

[temp.dep.expr]/2

this 是类型相关的,如果封闭的成员函数的类类型是相关的

因此this 在类模板的成员函数定义中是类型相关的。

[temp.dep.expr]/1

除了下面描述的,如果任何子表达式是类型相关的,则表达式是类型相关的。

但是,上述内容被问题中引用的 [temp.dep.expr]/5 中的异常所推翻。

this-&gt;m

表达式this-&gt;m 也不依赖于类型,因为它也是一个引用当前实例化成员的类成员访问表达式。

std::declval&lt;A&gt;().m

表达式std::declval&lt;A&gt;().m 必须依赖于类型,因为std::declval&lt;A&gt;() 的返回类型可能取决于A 的类型。

[temp.local]/1

与普通(非模板)类一样,类模板具有注入类名称(第 9 条)。注入的类- name 可以用作模板名称或类型名称。当它与模板参数列表一起使用时, 作为模板模板参数的模板参数,或作为详细类型说明符中的最终标识符 在友元类模板声明中,它指的是类模板本身。否则等价 到模板名称后跟&lt;&gt;中包含的类模板的模板参数

因此A 被转换为A&lt;T&gt;

[temp.dep.type]/8

一个类型是依赖的,如果它是

  • 模板参数,

  • 一个简单模板 ID,其中模板名称是模板参数或任何模板 arguments 是依赖类型或依赖于类型或值的表达式

这确认A&lt;T&gt; 是依赖类型,这意味着A 也是依赖类型。

[temp.dep.expr]/3

如果一个 id 表达式包含,则它是类型相关的

  • 一个依赖的模板 ID,

所以std::declval&lt;A&gt; 是一个依赖于类型的表达式。由此可见std::declval&lt;A&gt;().m 是类型相关的,因为它包含了类型相关的子表达式std::declval&lt;A&gt;

&amp;A::m

表达式&amp;A::m 在逻辑上必须依赖于类型,因为它的类型int A&lt;T&gt;::* 是依赖类型。

表达式&amp;A::m 被转换为&amp;A&lt;T&gt;::m,因为A 是注入的类名——如上所示。

根据 [temp.dep.expr]/3,id 表达式 A&lt;T&gt;::m 是类型相关的,因为它包含一个相关的模板 ID A&lt;T&gt;。因此,根据 [temp.dep.expr]/1,表达式 &amp;A&lt;T&gt;::m 与类型相关。

A::m

表达式A::m 被转换为A&lt;T&gt;::m,因为A 是注入的类名——如上所示。表达式A&lt;T&gt;::m 进一步转换为(*this).A&lt;T&gt;::m,因为A&lt;T&gt;::m 命名了A 的一个非静态成员。

根据 [temp.dep.expr]/3,id 表达式 A&lt;T&gt;::m 是类型相关的,因为它包含一个相关的模板 ID A&lt;T&gt;。类成员访问表达式 (*this).A&lt;T&gt;::m 引用当前实例化的成员,因此 [temp.dep.expr]/5 适用,但不会使表达式依赖于类型 - 也不与 [temp.dep.expr]/ 相矛盾3.

缺陷?

根据上面的解释,命名A 成员的id 表达式用AA&lt;T&gt; 限定将变得依赖于类型,这似乎是不必要且不一致的。考虑使用AA&lt;T&gt; 限定的类型名称不会依赖于相同的上下文。

如果 [temp.dep.expr]/5 的意图是 (*this).m 不依赖于类型,那么 (*this).A&lt;T&gt;::m 也不应该依赖于类型。另一方面,意图可能是非静态成员的名称始终是依赖的。我已经在标准讨论中发布了一个问题来澄清这一点:https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/gEvZ7mmXEC8

【讨论】:

  • ...我不知何故错过了整个事情都在一个成员函数中的事实。
  • 但是类成员访问表达式的类型依赖在[temp.dep.expr]/5中有所描述,优于[temp.dep.expr]/1中的一般规则。
  • @T.C.是的,[temp.dep.expr]/1 说“除了如下所述”
【解决方案2】:

正如您所说的那样,sizeof m 转换为 sizeof (*this).m
sizeof 仅在参数表达式依赖于类型时才依赖,而根据 [temp.dep. expr]/5:

一个类成员访问表达式 (5.2.5) 是依赖于类型的,如果 表达式是指当前实例化的成员和 被引用成员的类型是依赖的,或者类成员访问 表达式引用未知特化的成员。

m 的类型不依赖,并且表达式也不引用未知特化的成员 - [temp.dep.type]/6:

如果名字是未知专业化的成员,则它是

  • 一个 id-expression 表示类成员访问表达式 (5.2.5) 中的成员,其中任一
    • 对象表达式的类型是当前实例化,当前实例化至少有一个依赖基类,并且 id-expression 的名称查找未找到类的成员 即当前实例化或非依赖基类 其中;或
    • 对象表达式的类型是依赖的,不是当前的实例化。

即使(*this) 的类型是依赖的,它也是当前的实例化。并且名称查找应该发现​​m 是当前实例化的成员。

所以*this 不依赖于类型,因此sizeof (*this).m 不依赖。 (sizeof m 也不依赖于函数定义的任何非静态数据成员初始化程序之外,我在第二个已删除的答案中不小心提到了这一点)。


要使sizeof std::declval&lt;A&gt;().m 依赖,std::declval&lt;A&gt;().m 必须依赖于类型。
std::declval&lt;A&gt;().m 似乎 依赖于类型,但我不确定。正如我在上面引用的 [temp.dep.expr]/5 中所指定的,唯一的可能是表达式中的 m 是未知专业化的成员,我们必须证明它是。

如果名字是未知专业化的成员,则它是

  • 一个 id-expression 表示类成员访问表达式 (5.2.5) 中的成员,其中任一
    • 对象表达式的类型是当前实例化,当前实例化至少有一个依赖基类,并且 id-expression 的名称查找未找到类的成员 即当前实例化或非依赖基类 其中;或
    • 对象表达式的类型是依赖的,不是当前的实例化。

以下是事实:

  • 对象表达式std::declval&lt;A&gt;() 依赖于类型。

  • std::declval&lt;A&gt; 的查找仅在定义上下文中进行,因为它是一个 qualified-id,它永远不是 依赖名称([temp. dep]/1)。

通过限定名查找恰好找到一个declval 函数模板,但我们无法知道该候选的返回类型是否是定义时的当前实例化。特别是在这里,add_rvalue_reference 可能具有在定义时未知的专业化(类似于this one 的场景)。所以因为我们不知道std::declval&lt;A&gt;() 是否是当前实例化,(我们假设)它不是,这使得整个表达式依赖于类型。


&amp;A::m 的形式为&amp;qualified-id,由 [temp.dep.constexpr]/5:

覆盖

&amp;qualified-id 形式的表达式,其中 qualified-id 将当前实例化的依赖成员命名为 值依赖。

[temp.dep.type]/5:

一个名字是一个当前实例化的依赖成员如果它是一个 当前实例化的成员,在查找时指的是 at 当前实例化的类的至少一个成员。

显然A::m 是当前实例化的成员,因此&amp;A::m 是值相关的。
&amp;A::m 也与类型相关:根据 [temp.local],子表达式 A 等价于 A&lt;T&gt;,这是一个带有依赖模板参数的 simple-template-id。 p>

【讨论】:

  • 投反对票的人能否解释导致他采取行动的问题?
  • 顺便说一句,我是赞成票。看起来 [temp.dep.constexpr]/5 自 C++11 以来发生了显着变化。
  • .. 除非 [temp.dep.expr]/5 以某种方式覆盖 [temp.dep.expr]/3。即便如此,/5 也不适用于静态成员。
  • @willj 特别是 [temp.dep.expr]/(3.4) 是看起来不正确的部分:A&lt;T&gt; 显然是一个依赖类型,但 A&lt;T&gt;::m 不应该是类型-如果 m 是使用非依赖类型声明的成员,则依赖。
  • 我在回答中写下了这个问题,并在 std-discussion 上发布了一个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-20
  • 1970-01-01
  • 2021-12-03
  • 1970-01-01
  • 1970-01-01
  • 2014-04-14
相关资源
最近更新 更多