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

时间:2019-08-05 15:17:33

标签: c++ templates

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

https://gcc.godbolt.org/z/QNsgwd


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
    }
};

3 个答案:

答案 0 :(得分:3)

testxtesty的格式正确,因此gcc对于testx是错误的。但是标准对testva有点含糊。

最简单的方法是,在testy中,表达式&a<b>::y表示一个未重载的非模板函数,因此它的类型为void (a<b>::*)(int),无需进一步分析。从任何指针到成员函数到任何其他指针到成员函数的转换都是格式正确的reinterpret_cast,结果没有指定,除非转换回原始类型,并且C样式强制转换可以reinterpret_cast可以做什么。

对于模板功能,我们有[over.over]/1-2

  

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

     
      
  • ...

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

  •   
  • ...

  •   
     

如果名称是函数模板,则完成模板参数推导([temp.deduct.funcaddr]),如果参数推导成功,则使用生成的模板参数列表来生成单个函数模板特化,即添加到考虑的重载函数集。 [注意:如[temp.arg.explicit]中所述,如果推导失败并且函数模板名称后接显式模板参数列表,则 template-id 为然后检查以确定它是否标识单个功能模板专业化。如果是,则 template-id 被认为是该功能模板专门化的左值。在该确定中不使用目标类型。 — 尾注]

因此,这意味着我们首先尝试对FT进行模板参数推导,然后将其与目标类型a<b>::x<int>匹配。但是没有专长可以给出精确匹配,因为它们都有一个参数,而不是零,因此推论失败。但根据注释,还有[temp.arg.explicit](C ++ 17中的第3段,4 in the latest C++20 draft):

  

可以从默认模板参数推论或获得的跟踪模板参数可以从显式模板参数列表中省略。否则不推导出的尾随模板参数包([temp.variadic])将被推导为模板参数的空序列。 ...在演绎完成且失败的情况下,或在演绎未完成的情况下,如果指定了模板参数列表,并且该模板参数列表与任何默认模板参数一起标识了单个函数模板特化,则 template-id 是功能模板专业化的左值。

void (a<b>::*)()中, template-id testx标识单个功能模板特化。因此,它命名为专门化名称,并且C样式强制转换仍然有效且结果未指定。

因此在a<b>::x<int>中,testva是否标识单个专业化?当然可以使用该表达式通过[temp.arg.explicit]/9来命名不同的专业化名称:

  

模板参数推导可以扩展与模板参数包相对应的模板参数的序列,即使序列包含显式指定的模板参数也是如此。

除此以外,表示“模板参数推导”。此处涉及的模板参数推导失败,因为它要求与目标类型a<b>::va<int>进行不可能的匹配。因此,void (a<b>::*)()不能真正地标识a<b>::va<int>是否标识单个专业化,因为没有描述获取其他模板参数的其他方法,也不能标识多个专业化,因为它可以有效地用于具有匹配目标类型的其他上下文中。

答案 1 :(得分:0)

c是对的

  

[expr.reinterpret.cast]/10

     

可以将类型“指向类型X的{​​{1}}成员的指针的prvalue显式转换为类型” {{ 1}}”,如果T1Y都是函数类型或都是对象类型。空成员指针值将转换为目标类型的空成员指针值。除以下情况外,未指定此转换的结果:

     
      
  • 将“指向成员函数的指针”类型的prvalue转换为不同的指向成员函数的指针类型,并返回其原始类型将产生原始的指向成员函数的值。
  •   
  • 将类型“将指针指向类型T2的{​​{1}}的数据成员的prvalue转换为类型”将类型指向T1的{​​{1}}的数据成员的指针”( T2的对齐要求不严格于X的对齐要求,然后返回其原始类型将产生原始的指针到成员值。
  •   

T1等。是类型为“指向类型为Y的{​​{1}}成员的指针的 prvalue ”并将其转换(无需调用未指定值的结果函数指针)是合法的。

答案 2 :(得分:0)

让我们简化为该示例:

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

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

    void y(int) {}
};

using no_args = void(a::*)();
using int_arg = void(a::*)(int);

让我们尝试以下四件事:

reinterpret_cast<no_args>(&MEMBER_FUNCTION);  // (1)
(no_args) &MEMBER_FUNCTION;  // (2)
(no_args) static_cast<int_arg>(&MEMBER_FUNCTION);  // (3)
int_arg temp = &MEMBER_FUNCTION; (no_args) temp;  // (4)

(用MEMBER_FUNCTION&a::va<int>&a::x<int>替换&a::y)。

clang编译它们全部。
gcc使用&a::va<int>&a::x<int>编译(2)以外的所有内容。
MSVC使用&a::va<int>编译(1)和(2)之外的所有内容(但使用&a::x<int>可以编译)。

请注意,(3)与(4)基本上相同。

https://gcc.godbolt.org/z/a2qqyo显示了一个示例。

从中您可以看到,&MEMBER_FUNCTION在模板的情况下不会解析为特定的成员函数指针,但是如果解析,则可以将其重新解释为另一种成员函数指针类型。 / p>

该标准怎么说:

[over.over]/1

  

在某些情况下,对不带参数的重载函数名的使用将解析为函数,重载集中特定函数的函数指针或成员函数指针。   在这种情况下,功能模板名称被认为是命名一组重载功能。   如果F(可能在应用了函数指针转换之后)与FT相同,则为上下文中所需的目标类型的函数类型FT选择类型为F的函数。   [注:也就是说,当与成员函数指针类型匹配时,该函数是成员的类将被忽略。   —注   ]   目标可以是:
  [...]
  -显式类型转换([expr.type.conv],[expr.static.cast],[expr.cast])

稍后给出的示例是:

int f(double);
int f(int);
void g() {
  (int (*)(int))&f;             // cast expression as selector
}

还有关于模板的更多引用:

[temp.deduct.funcaddr]/1

  

可以从采用重载函数的地址时指定的类型推导模板参数。   函数模板的函数类型和指定的类型用作P和A的类型,并按照[temp.deduct.type]中的描述进行推导。

[temp.arg.explicit]/4

  

[...]如果指定了模板参数列表,并且它与任何默认模板参数一起标识了单个函数模板特殊化,则template-id是函数模板特殊化的左值。

似乎MSVC是正确的。

除非您将

&a::va<int>分配/广播到void(a::*)(int),否则它不会解析。您还应该能够将其分配给void(a::*)(int, char)void(a::*)(int, double, char),其中Args分别推导为{ int, char }{ int, double, char }。这意味着(no_args) &a::va<int> 应该失败,因为可能有Args的许多集合(所有集合都以int开头,而clang则过分地解决了它) ),并且它们都不采用零参数,因此(no_args) &a::va<int>是应该失败的static_cast

对于&a::x<int>,只有一个可能的功能,因此它应该与&a::y完全一样地工作(但是gcc尚未解决它)。

相关问题