C++ 将成员函数转换为指向成员函数的指针

C++ 将成员函数转换为指向成员函数的指针,c++,templates,C++,Templates,Clang、GCC、MSVC对成员函数的转换有不同的看法。 谁是对的 模板 结构a { 模板 void va(Args…{} 模板 空位x(x){} 空y(int){} }; 结构b:a { void testva() { 使用F=void(a::*)(); F=(F)&a::va;//gcc:error,msvc:error,clang:ok } void testx() { 使用F=void(a::*)(); F=(F)&a::x;//gcc:error,msvc:ok,clang:ok

Clang、GCC、MSVC对成员函数的转换有不同的看法。 谁是对的


模板
结构a
{
模板
void va(Args…{}
模板
空位x(x){}
空y(int){}
};
结构b:a
{
void testva()
{
使用F=void(a::*)();
F=(F)&a::va;//gcc:error,msvc:error,clang:ok
}
void testx()
{
使用F=void(a::*)();
F=(F)&a::x;//gcc:error,msvc:ok,clang:ok
}
void testy()
{
使用F=void(a::*)();
F=(F)&a::y;//gcc:ok,msvc:ok,clang:ok
}
};
clang是对的 如果
T1
类型的“指向
X
成员的指针”和
T2
类型的“指向
Y
成员的指针”都是函数类型或对象类型,则可以将类型为“指向
T2
类型的成员的指针”的prvalue显式转换为不同类型的prvalue。空成员指针值将转换为目标类型的空成员指针值。此转换的结果未指定,以下情况除外:

  • 将“指向成员函数的指针”类型的prvalue转换为指向成员函数类型的不同指针并返回其原始类型将生成指向成员值的原始指针
  • T1
    类型的“指向
    X
    的数据成员的指针”类型的PR值转换为
    T2
    类型的“指向
    Y
    的数据成员的指针”(其中
    T2
    的对齐要求并不比
    T1
    的对齐要求更严格)返回到其原始类型将生成指向成员值的原始指针

&a::va
等。类型为“指向
a
成员的
a
类型为
void(int)
”的指针的prvalue并将其转换(不调用未指定值的结果函数指针)是合法的。

testx
testy
格式正确,因此gcc对
testx
的理解是错误的。但是该标准在
testva
方面有些模糊

从最简单的开始,在
testy
中,表达式
&a::y
命名了一个未重载的非模板函数,因此它的类型为
void(a::*)(int)
,无需进一步分析。从任何指向成员函数的指针到任何其他指向成员函数的指针的转换都是格式良好的
重新解释类型转换
,除非转换回原始类型,否则会产生未指定的结果,C样式转换可以做
重新解释类型转换
可以做的事情

对于模板函数,我们有:

在某些上下文中,不带参数的重载函数名的使用解析为重载集中特定函数的函数、函数指针或成员函数指针。函数模板名称被认为是在这样的上下文中命名一组重载函数。如果
F
(可能应用函数指针转换后)与
FT
相同,则为上下文中所需目标类型的函数类型
FT
选择类型为
F
的函数。目标可以是

  • 显式类型转换([expr.type.conv]、[expr.static.cast]、[expr.cast])

如果名称是函数模板,则会执行模板参数推断([temp.decrete.funcaddr]),如果参数推断成功,则生成的模板参数列表将用于生成单个函数模板专用化,并将其添加到所考虑的重载函数集中。[ 注:如[temp.arg.explicit]中所述,如果演绎失败,且函数模板名称后面跟着一个显式模板参数列表,则会检查模板id是否标识单个函数模板专用化。如果标识了,则模板id将被视为该函数模板专用化的左值。该数据中未使用目标类型终止- 尾注 ]

这意味着我们首先尝试对
a::x
进行模板参数推断,将其与目标类型
void(a::*)()
匹配。但是没有专门化可以提供精确匹配,因为它们都有一个参数,而不是零,因此推断失败。但是根据注释,还有[temp.arg.explicit](C++17中的第3段,):

可以从显式模板参数列表中忽略可以从默认模板参数推导或获取的后续模板参数。后续模板参数包([temp.variadic])如果指定了模板参数列表,且该列表与任何默认模板参数一起标识单个函数模板专用化,则模板id为n函数模板专用化的左值

testx
中,模板id
a::x
标识单个函数模板专门化。因此它命名该专门化,并且C样式转换同样有效,结果未指定

那么在
testva
中,
a::va
是否识别一个专门化?当然可以通过以下方式使用该表达式命名不同的专门化:

模板参数推断可以扩展与模板参数包对应的模板参数序列,即使该序列包含显式指定的模板参数

除了上面说的“模板参数推断”。这里涉及的模板参数推断失败了,因为它需要与目标类型
void(a::*)()
进行不可能的匹配。因此,没有任何东西真正解释
a::va
是否识别单个专门化,因为没有其他获取

template<typename T>
struct a
{
    template <typename... Args>
    void va(Args...) {}

    template <typename X>
    void x(X) {}

    void y(int) {}
};

struct b : a<b>
{
    void testva()
    {
        using F = void (a<b>::*)();

        F f = (F)&a<b>::va<int>; // gcc: error, msvc: error, clang: ok
    }

    void testx()
    {
        using F = void (a<b>::*)();

        F f = (F)&a<b>::x<int>;// gcc: error, msvc: ok, clang: ok
    }

    void testy()
    {
        using F = void (a<b>::*)();

        F f = (F)& a<b>::y; // gcc: ok, msvc: ok, clang: ok
    }
};