【问题标题】:C++0x confusion with using declarationsC++0x 与 using 声明的混淆
【发布时间】:2011-08-06 17:20:12
【问题描述】:

这种情况应该怎么办:

struct A {
  void f();
};

struct B : virtual A {
  using A::f;
};

struct C : virtual A {
  using A::f;
};

struct D : B, C { 
  void g() {
    f();
  }
};

感兴趣的行是f()。显然,根据 FDIS 的10.2 查找f 成功并找到A::f。但是,超载决议会考虑哪些候选人?规范在13.3.1p4

对于通过 using 声明引入派生类的非转换函数,该函数被认为是派生类的成员,以便定义隐式对象参数的类型。

这样做的目的是,对于单个类,如果此类包含自己的成员函数和将基类函数的名称带入作用域的 using 声明,则在重载决策期间,所有候选函数都具有相同的类类型它们的隐含对象参数。但这对上面的例子意味着什么?候选人会是以下吗?

void F1(B&)
void F2(C&)
// call arguments: (lvalue D)

这似乎是错误的,因为根据10.2p7,我们在查找结果集中只有一个声明。我们该如何解释?

【问题讨论】:

    标签: c++ c++11 overload-resolution using-declaration


    【解决方案1】:

    我认为,由于 10.2/7 生成的查找集仅导致一个声明,因此根本不存在函数重载。 13.3.1/4 仅适用于/如果从 10.2/7 产生的查找集包含两个或更多声明。

    编辑:也许我并没有我希望的那么清楚。即使fA 中超载,我认为大多数相同的推理都适用。也许最好一步一步来。 (注意,在这种情况下,我使用与标准相同的 S(f, X) 表示法,但由于您的派生类最多的是 D,因此您的 S(f, D) 对应于它们的 S(f, C) ,并且您的 S(f, B) 和 S(f, C) 对应于它的 S(f, B1) 和 S(f, B2)。

    首先 s(f, D) 是空的,因为我们没有直接包含在 D 中的 f 的声明。基于此,我们得到 10.2/5。

    在 10.2/6 中,我们首先将 s(f, B) 合并到 S(f, D) 中。由于 s(f, D) 当前为空,我们遵循第一个要点下的第二个条件,S(f, D) 成为 S(f, B) 的副本。

    然后我们必须将 S(f, C) 合并到 S(f, D) 中。在这种情况下,S(f, C) 的每个子对象成员都是 S(f, D) 的子对象成员。这样就满足了第一个要点的第一个条件,所以我们保持S(f, D)不变,合并就完成了。

    此时,不再需要考虑基类 Bi,因此我们的 S(f, D) = S(f, B)。 没有来自 S(f, C) 的声明出现在最终的重载集中根本

    然后,如果 S(f, B) 包含两个或更多函数,我们继续 13.3.1,并解决重载集 - 但由于整个集来自 B,问题中提出的情况根本不不存在。

    【讨论】:

    • @Jerry 感谢您的回答。这是规范不清楚的一点。例如,13.3.1.1.1 说“对 13.3.1.1.1 感兴趣的只是那些后缀表达式最终包含一个名称的函数调用,该名称表示一个或多个可能被调用的函数。” (另见eggheadcafe.com/software/aspnet/36285261/…)。无论如何,我们可以在A 中声明第二个函数,比如void f(int);,然后肯定会应用重载决议。对于问题中提出的问题,我认为这没有什么不同。
    • "每个 S(f, C) 的子对象成员都是 S(f, D) 的子对象成员" -> 此时S(f, D) 的状态为{ { A::f }, { B in D } },并且S(f, C){ { A::f }, { C in D } } 所以我不明白为什么会这样(C 不是 B 的子对象,反之亦然)。你能解释一下那部分吗?
    • @Johannes:为了确保我理解你,你认为最后一个要点应该适用,我们应该形成主题集/共享声明集的联合,对吗?
    • @Jerry 是的,这就是我认为应该做的。我认为第一个项目符号是执行支配规则(在第 10 段的注释中描述)。在我们的例子中,没有支配地位。
    • @Johannes:我的想法(可能是错误的)是它谈论的是 Bi 的 子对象成员,而不是 Bi 本身,所以我认为该集合包括 A ,它 C 的子对象。但是,重读它,尚不清楚(至少对我而言)它们是指集合的成员还是类的成员。我把它读作是指类成员,但我认为你可能是对的,它旨在指代集合成员。
    【解决方案2】:

    只是推测,完全不确定。 :)

    [ Example:
    struct A { int x; }; // S(x,A) = { { A::x }, { A } }
    struct B { float x; }; // S(x,B) = { { B::x }, { B } }
    struct C: public A, public B { }; // S(x,C) = { invalid, { A in C, B in C } }
    struct D: public virtual C { }; // S(x,D) = S(x,C)
    struct E: public virtual C { char x; }; // S(x,E) = { { E::x }, { E } }
    struct F: public D, public E { }; // S(x,F) = S(x,E)
    int main() {
    F f;
    f.x = 0; // OK, lookup finds E::x
    }
    S(x, F) is unambiguous because the A and B base subobjects of D are also base subobjects of E, so S(x,D)
    is discarded in the first merge step. —end example ]
    

    是 10.2p7 中的示例,其中S(f,C) 表示查找集。最后提供的句子是必不可少的:由于DE 具有相同的C 基类,并且E::x 隐藏 x 从那个C,使得F::x 的最终用法明确。
    现在,在您的示例中,没有任何东西隐藏D 的基类的f,因此D::f 的使用仍然模棱两可,我看不出10.2p7 如何适用于您的情况。就像楼上说的,完全不确定。 ;)

    【讨论】:

    • p3 说“在声明集中,using-declarations 被它们指定的成员替换”,所以我们总是只会找到A::f,所以最终结果是{ { A::f }, { B in D, C in D } }
    • 来自您的问题:“对于由 using 声明引入派生类的非转换函数,该函数被认为是派生类的成员”。那么这不适用,所以会有{ { B::f, C::f }, { B in D, C in D } },因此它会模棱两可吗?
    【解决方案3】:

    我不直接解决这个问题,而是试图争辩说,在每个派生类中假装存在 f 函数是行不通的:

    只有一个候选函数,它有类型

    void A::(void)
    

    尽管您可以使用

    形成指向该函数的成员指针
    void (A::*F0)(void) = &A::f;
    void (B::*F1)(void) = F0;
    void (C::*F2)(void) = F0;
    

    这是因为指向成员的指针包含计算函数参数所需的附加信息。指向成员的指针调用站点找到实际目标实例的A 子对象,以提供fthis 指针。函数内部没有从派生类型的this 指针中查找A 的成员的逻辑。所以不能像你的问题所暗示的那样谈论void F1(B* this)void F2(C* this)

    如果函数被认为是派生类的成员,则为

    void B::A::f(void);
    void C::A::f(void);
    

    但是由于B::AC::A是同一个基类,最终候选列表中只有一个函数,尽管它在列表中出现了两次。然后虚拟继承提供了两个候选者在同一个对象上调用同一个函数,没有歧义。

    【讨论】:

    • 但是,我看到的问题是 一个 候选函数是“通过 using 声明引入派生类”两次。考虑struct A { void f(short); }; struct B : A { void f(long); using A::f; };。现在,B b; b.f(0); 将是模棱两可的,因为两个候选对象都将 B& 作为隐式对象参数:A::f 被认为是 B 的成员。但是,在我的问题示例中,我们认为 A::f 是哪个成员?
    • 另外,我不知道这与指向成员的指针有什么关系。
    • @johannes: 指向成员的指针允许您将A::f 视为接受B* 类型的隐藏this 参数。但这依赖于指向成员的指针中的额外机制,实际函数不会将 B* 作为其 this 指针,并且不能将其视为。
    • 参见 13.3.1 和隐式对象参数的描述。另外,我不知道您所说的“如果函数被认为是派生类的成员,则它是......”。
    【解决方案4】:

    我认为关键在于 10.2p5,其中的标准是指检查每个“直接基类子对象”。

    因为A 是虚拟继承的,所以它是D (10.1p4) 的“直接基类子对象”。

    然后考虑D 的三个子对象:ABC。到 10.2p6,BC 被淘汰(A 是这些的基础),只有 A::f 是候选者。

    【讨论】:

      猜你喜欢
      • 2023-03-12
      • 2022-01-13
      • 1970-01-01
      • 1970-01-01
      • 2010-11-24
      • 1970-01-01
      • 2012-08-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多