使用父级方法作为派生类方法时的海湾频道错误

发布于 2025-01-26 06:20:43 字数 1720 浏览 3 评论 0原文

我的代码中有一个功能,该功能仅接受类成员方法作为模板参数。我需要使用从父类继承的类方法调用此方法。这是我问题的示例代码:

template <class C>
class Test {
public:
    template<typename R, R( C::* TMethod )()> // only a member function should be accepted here
    void test() {} 
};

class A {
    public:
    int a() { return 0; } // dummy method for inheritance
};

class B : public A {
public:
    using A::a; // A::a should be declared in the B class declaration region

    // int a() { return A::a(); } // if this lines is activated compliation works
};

int main() {
    auto t = Test<B>();

    t.test<int, &B::a>();
}

使用MSVC 2019编译器,无问题编译代码。但是,海湾合作委员会会产生以下错误:

<source>: In function 'int main()':
<source>:23:23: error: no matching function for call to 'Test<B>::test<int, &A::a>()'
   23 |     t.test<int, &B::a>();
      |     ~~~~~~~~~~~~~~~~~~^~
<source>:5:10: note: candidate: 'template<class R, R (B::* TMethod)()> void Test<C>::test() [with R (C::* TMethod)() = R; C = B]'
    5 |     void test() {}
      |          ^~~~
<source>:5:10: note:   template argument deduction/substitution failed:
<source>:23:17: error: could not convert template argument '&A::a' from 'int (A::*)()' to 'int (B::*)()'
   23 |     t.test<int, &B::a>();
      |    

据我了解,海湾合作委员会仍在处理B :: A作为A :: A的类型。在CPP中引用了它的说法

将其他位置定义的名称引入声明性 出现这种使用量的区域。

因此,我认为使用应将A ::一种方法转移到B的Declerativ区域,因此应将其处理为B :: a。我错了还是海湾合作委员会中有错误?

这是编译器资源管理器上的示例: https://godbolt.org.org/z/ttrd189swssw

I have a function in my code which only accepts a class member method as a template parameter. I need to call this method using a class method which is inherited from a parent class. Here is an example code of my problem:

template <class C>
class Test {
public:
    template<typename R, R( C::* TMethod )()> // only a member function should be accepted here
    void test() {} 
};

class A {
    public:
    int a() { return 0; } // dummy method for inheritance
};

class B : public A {
public:
    using A::a; // A::a should be declared in the B class declaration region

    // int a() { return A::a(); } // if this lines is activated compliation works
};

int main() {
    auto t = Test<B>();

    t.test<int, &B::a>();
}

With the MSVC 2019 compiler the code compiles without problems. However the gcc produces following error:

<source>: In function 'int main()':
<source>:23:23: error: no matching function for call to 'Test<B>::test<int, &A::a>()'
   23 |     t.test<int, &B::a>();
      |     ~~~~~~~~~~~~~~~~~~^~
<source>:5:10: note: candidate: 'template<class R, R (B::* TMethod)()> void Test<C>::test() [with R (C::* TMethod)() = R; C = B]'
    5 |     void test() {}
      |          ^~~~
<source>:5:10: note:   template argument deduction/substitution failed:
<source>:23:17: error: could not convert template argument '&A::a' from 'int (A::*)()' to 'int (B::*)()'
   23 |     t.test<int, &B::a>();
      |    

As far as I understand the gcc is still handling the type of B::a as A::a. On the cpp reference its saying that using

Introduces a name that is defined elsewhere into the declarative
region where this using-declaration appears.

So in my opinion the using should transfer the A::a method to the declerativ region of B and therefor it should be handled as B::a. Am I wrong or is there a bug in GCC?

Here is the example on Compiler Explorer: https://godbolt.org/z/TTrd189sW

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

不气馁 2025-02-02 06:20:43

namespace.udecl ,项目12,项目12(重点是我的):

目的是在超载期间组建一组候选人
分辨率,由派生中使用命名的功能
班级被对待好像他们是派生的直接成员
班级。 [...] 这对函数的类型没有影响,
在所有其他方面

因此,a不是b的成员,&amp; b :: a的类型是 INS int(a ::::: *)()
&amp; b :: a无论您是否包括使用a :: a;>)表示同一件事,

使用没有意义代码>来自基类的命名函数,除了要超载或覆盖它们时围绕“隐藏问题”工作。

There is namespace.udecl, item 12 (emphasis mine):

For the purpose of forming a set of candidates during overload
resolution, the functions named by a using-declaration in a derived
class are treated as though they were direct members of the derived
class. [...] This has no effect on the type of the function,
and in all other respects the function remains part of the base class
.

Thus, a is not a member of B, and the type of &B::a is int (A::*)().
(&B::a means the same thing regardless of whether you include using A::a; or not)

There is no point to using named functions from a base class except to work around the "hiding problem" when you want to overload or override them.

当梦初醒 2025-02-02 06:20:43

(non-nullptr)在转换的常数表达式中不允许使用指针到会员转换

因此,我认为使用应将a ::一种方法传输到B的解剖区域,因此应将其处理为b :: a。我错了还是GCC中有错误?

您错了,但是我们需要稍微降低语言规则兔子孔以找出原因。

首先,即使是通过派生类引用的指针的 type (即使是通过使用声明引入)也是指针到成员的基础。 ) ]/3 明确涵盖此用例:

一般&amp;操作员是通往其操作数的指针。

  • (3.1)如果操作数是合格的ID命名某些类T类C的非静态或变体成员M,则结果具有类型
    “指向T类C类的指针”,是一个指定
    c :: m。
  • [...]

[示例1:

  struct a {int i; };
结构B:a {};
...&amp; b :: i ... //具有type int a :: *&lt;  -   - !
int a;
int* p1 =&amp; a;
int* p2 = p1 + 1; //定义的行为
bool b = p2&gt; P1; //定义的行为,价值为真
 

- 结束示例]

但是 。 (衍生的):

类型“指向CV type t type cv t的指针”的序言,其中B是类型类型,可以转换为“指向C型C型d的指针”类型的prvalue,其中D是d从B中得出的完整类([[class.dedrived])。如果B是无法访问的([class.access]),模棱两可([class.member.lookup])或virtual([class.mi])d ,或d的虚拟基类的基类,必须将这种转换的程序形成不佳。转换的结果是指与转换发生之前的指针相同的成员,但它指的是基类成员,就好像它是派生类的成员一样。结果是指B中的成员。由于结果的类型为“指向CV T型D的指针”,因此通过其D对象的间接方式有效。结果与使用D的B子对象的指针进行间接。

也就是说,可以将基础类成员的指针转换为派生类的成员,实际上是以下程序,其中转换是在参数(基本的指针到成员)的上下文中进行函数参数的(类型:派生的指针到成员)是良好的:

struct A {
    int a() { return 0; };
};

struct B : A {};

void f(int( B::*)()) {}

int main() {
    f(&A::a);  // OK: [conv.mem]/2
}

为什么模板参数的情况失败?一个最小的示例是:

struct A {
    int a() { return 0; };
};

struct B : A {};

template<int(B::* TMethod )()>
void g() {}

int main() {
    g<&A::a>();  // error
}

根本原因是模板参数扣除失败:模板参数为&amp; a :: a type int(a ::**)()适用:

非类型模板参数的模板题词应为模板参数类型的转换常数表达式([Expr.const])。

在转换的常数表达式中不允许A(非零PTR)指针到会员转换([[Cons.Mem]/2)(请参阅 [expr.const]/10 ),意思类型int(b ::*)()()的非类型模板参数。

我们可能会注意到,如果我们更改为类模板,Clang实际上为我们提供了非常清晰的诊断:

struct A {
    int a() { return 0; };
};

struct B : A {};

template<int(B::*)()>
struct C {};

int main() {
    C<&A::a> c{};
    // error: conversion from 'int (A::*)()' to 'int (B::*)()' 
    //        is not allowed in a converted constant expression
}

(Non-nullptr) pointer-to-member conversions are not allowed in converted constant expressions

So in my opinion the using should transfer the A::a method to the declerative region of B and therefor it should be handled as B::a. Am I wrong or is there a bug in GCC?

You are wrong, but we'll need to go a bit down the language rules rabbit hole to find out why.

First of all the type of the pointer to member, even when referred to via the derived class (even when introduced via a using declaration), is that of pointer-to-member-of-base. The (non-normative) example of [expr.unary.op]/3 explicitly covers this use case:

The result of the unary & operator is a pointer to its operand.

  • (3.1) If the operand is a qualified-id naming a non-static or variant member m of some class C with type T, the result has type
    “pointer to member of class C of type T” and is a prvalue designating
    C​::​m.
  • [...]

[Example 1:

struct A { int i; };
struct B : A { };
... &B::i ...       // has type int A​::​*  <-- !!!
int a;
int* p1 = &a;
int* p2 = p1 + 1;   // defined behavior
bool b = p2 > p1;   // defined behavior, with value true

— end example]

However [conv.mem]/2 covers that you may convert int (A::*)() (base) to int (B::*)() (derived):

A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a prvalue of type “pointer to member of D of type cv T”, where D is a complete class derived ([class.derived]) from B. If B is an inaccessible ([class.access]), ambiguous ([class.member.lookup]), or virtual ([class.mi]) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion refers to the same member as the pointer to member before the conversion took place, but it refers to the base class member as if it were a member of the derived class. The result refers to the member in D's instance of B. Since the result has type “pointer to member of D of type cv T”, indirection through it with a D object is valid. The result is the same as if indirecting through the pointer to member of B with the B subobject of D. The null member pointer value is converted to the null member pointer value of the destination type.

Namely, a pointer to member of a base class may be converted to member of derived class, and indeed, the following program, where conversion is made in the context of an argument (pointer-to-member of base) to a function parameter (type: pointer-to-member of derived) is well-formed:

struct A {
    int a() { return 0; };
};

struct B : A {};

void f(int( B::*)()) {}

int main() {
    f(&A::a);  // OK: [conv.mem]/2
}

Then why does the case of a template parameter fails? A more minimal example is:

struct A {
    int a() { return 0; };
};

struct B : A {};

template<int(B::* TMethod )()>
void g() {}

int main() {
    g<&A::a>();  // error
}

The root cause is that template argument deduction fails: the template argument is &A::a of type int(A::*)() and [temp.arg.nontype]/2 applies:

A template-argument for a non-type template-parameter shall be a converted constant expression ([expr.const]) of the type of the template-parameter.

A (non-nullptr) pointer-to-member conversion ([conv.mem]/2) is not allowed in a converted constant expression (refer to [expr.const]/10), meaning &A::a is not a valid template argument to a non-type template parameter of type int(B::*)().

We may note that Clang actually gives us a very clear diagnostic on this if we change to class template:

struct A {
    int a() { return 0; };
};

struct B : A {};

template<int(B::*)()>
struct C {};

int main() {
    C<&A::a> c{};
    // error: conversion from 'int (A::*)()' to 'int (B::*)()' 
    //        is not allowed in a converted constant expression
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文