简体   繁体   English

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

[英]convert member function to pointer to member function

Clang,GCC,MSVC have different opinion about conversion of member functions. Clang,GCC,MSVC对成员函数的转换有不同的看法。 Who is right ? 谁是对的?

https://gcc.godbolt.org/z/QNsgwd 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
    }
};

testx and testy are well-formed, so gcc is wrong about testx . testxtesty格式正确,因此gcc对testx是错误的。 But the Standard is somewhat vague about testva . 但该标准对于testva有些模糊。

Starting with the easiest, in testy the expression &a<b>::y names a non-template function which is not overloaded, so it has type void (a<b>::*)(int) without need for further analysis. 用最简单的开始,在testy表达&a<b>::y名称的非模板函数,它不过载,所以它有键入void (a<b>::*)(int) ,而不需要进一步的分析。 Conversion from any pointer-to-member-function to any other pointer-to-member-function is a well-formed reinterpret_cast with unspecified results except if converted back to the original type, and a C-style cast can do what a reinterpret_cast can do. 从任何指向成员函数的指针转换到任何其他指向成员函数的指针都是格式正确的reinterpret_cast其中包含未指定的结果,除非转换回原始类型,并且C样式转换可以执行reinterpret_cast可以执行的操作做。

For template functions we have [over.over]/1-2 : 对于模板函数,我们有[over.over] / 1-2

A use of an overloaded function name without arguments is resolved in certain contexts to a function, a pointer to function or a pointer to member function for a specific function from the overload set. 在某些上下文中,使用不带参数的重载函数名称解析为函数,指向函数的指针或指向来自重载集的特定函数的成员函数的指针。 A function template name is considered to name a set of overloaded functions in such contexts. 函数模板名称被认为是在这种上下文中命名一组重载函数。 A function with type F is selected for the function type FT of the target type required in the context if F (after possibly applying the function pointer conversion) is identical to FT . 与A类型功能F被选择用于功能类型FT在上下文需要,如果目标类型的F (后可能将所述函数指针转换)是相同的FT The target can be 目标可以是

  • ... ...

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

  • ... ...

If the name is a function template, template argument deduction is done ([temp.deduct.funcaddr]), and if the argument deduction succeeds, the resulting template argument list is used to generate a single function template specialization, which is added to the set of overloaded functions considered. 如果名称是函数模板,则完成模板参数推导([temp.deduct.funcaddr]),如果参数推导成功,则生成的模板参数列表用于生成单个函数模板特化,并将其添加到考虑重载函数集。 [ Note: As described in [temp.arg.explicit], if deduction fails and the function template name is followed by an explicit template argument list, the template-id is then examined to see whether it identifies a single function template specialization. [ 注意:如[temp.arg.explicit]中所述,如果演绎失败且函数模板名称后跟显式模板参数列表,则会检查template-id以查看它是否标识单个函数模板特化。 If it does, the template-id is considered to be an lvalue for that function template specialization. 如果是,则模板ID被认为是该功能模板特化的左值。 The target type is not used in that determination. 目标类型不用于该确定。 end note ] - 结束说明 ]

So this means we first try template argument deduction for a<b>::x<int> , matching it to the target type void (a<b>::*)() . 所以这意味着我们首先尝试对a<b>::x<int>进行模板参数推导,将其与目标类型void (a<b>::*)()相匹配。 But there are no specializations that can possibly give an exact match, since they all have one argument, not zero, so deduction fails. 但是没有专业化可以给出完全匹配,因为它们都有一个参数,而不是零,因此演绎失败。 But per the note, there's also [temp.arg.explicit] (paragraph 3 in C++17, 4 in the latest C++20 draft ): 但是根据说明,还有[temp.arg.explicit](C ++ 17中的第3段, 最新的C ++ 20草案中的4 ):

Trailing template arguments that can be deduced or obtained from default template-arguments may be omitted from the list of explicit template-arguments. 可以从显式模板参数列表中省略可以从默认模板参数推导或获得的尾随模板参数。 A trailing template parameter pack ([temp.variadic]) not otherwise deduced will be deduced as an empty sequence of template arguments. 不以其他方式推导出的尾随模板参数包([temp.variadic])将被推断为模板参数的空序列。 ... In contexts where deduction is done and fails, or in contexts where deduction is not done, if a template argument list is specified and it, along with any default template arguments, identifies a single function template specialization, then the template-id is an lvalue for the function template specialization. ...在演绎完成和失​​败的上下文中,或在未进行演绎的上下文中,如果指定了模板参数列表,并且它与任何默认模板参数一起标识单个函数模板特化,则模板ID是函数模板特化的左值。

In testx , the template-id a<b>::x<int> identifies a single function template specialization. testxtemplate-id a<b>::x<int>标识单个函数模板特化。 So it names that specialization, and again the C-style cast is valid with unspecified result. 因此它命名为专门化,并且C样式转换也是有效的,具有未指定的结果。

So in testva , does a<b>::va<int> identify a single specialization? 那么在testvaa<b>::va<int>确定了一个专业化? It would certainly be possible to use that expression to name different specializations, via [temp.arg.explicit]/9 : 通过[temp.arg.explicit] / 9 ,肯定可以使用该表达式来命名不同的特化

Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments. 模板参数推导可以扩展与模板参数包对应的模板参数序列,即使序列包含显式指定的模板参数也是如此。

Except this says "template argument deduction". 除了这个说“模板参数演绎”。 And here the template argument deduction involved fails, since it required an impossible match with the target type void (a<b>::*)() . 这里涉及的模板参数推导失败,因为它需要与目标类型void (a<b>::*)()进行不可能的匹配。 So nothing really explains whether a<b>::va<int> identifies a single specialization, since no other method of getting additional template arguments is described, or identifies multiple specializations, since it could be validly used in other contexts with matching target types. 因此没有真正解释a<b>::va<int>标识单个特化,因为没有描述获取其他模板参数的其他方法,或者标识多个特化,因为它可以在具有匹配目标类型的其他上下文中有效使用。

clang is right 铿锵是对的

[expr.reinterpret.cast]/10

A prvalue of type “pointer to member of X of type T1 ” can be explicitly converted to a prvalue of a different type “pointer to member of Y of type T2 ” if T1 and T2 are both function types or both object types. 如果T1T2都是函数类型或两种对象类型,则可以将类型为“ T1类型X的成员的指针”的prvalue显式转换为不同类型的prvalue“指向类型为T2Y的成员的指针”。 The null member pointer value is converted to the null member pointer value of the destination type. 空成员指针值将转换为目标类型的空成员指针值。 The result of this conversion is unspecified, except in the following cases: 除以下情况外,此转换的结果未指定:

  • Converting a prvalue of type “pointer to member function” to a different pointer-to-member-function type and back to its original type yields the original pointer-to-member value. 将“指向成员函数的指针”类型的prvalue转换为不同的指向成员函数类型的指针并返回其原始类型会生成原始指向成员的值。
  • Converting a prvalue of type “pointer to data member of X of type T1 ” to the type “pointer to data member of Y of type T2 ” (where the alignment requirements of T2 are no stricter than those of T1 ) and back to its original type yields the original pointer-to-member value. 转换类型的prvalue“指针的数据成员X型的T1 ”的类型“指针的数据成员Y类型的T2 ”(其中的对准要求T2并不比那些更严格的T1 ),并返回到其原始type产生原始指向成员的值。

&a<b>::va<int> et al. &a<b>::va<int> 等。 are prvalue of type "pointer to member of a<b> of type void(int) " and converting it (without calling the resulting function pointer whose value is unspecified) is legal. 是“类型为void(int)类型a<b>成员的指针”类型的prvalue并且转换它(不调用其值未指定的结果函数指针)是合法的。

Let's simplify to this example: 让我们简化一下这个例子:

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

And lets try the following four things: 让我们尝试以下四件事:

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)

(Replacing MEMBER_FUNCTION with &a::va<int> , &a::x<int> and &a::y ). (用&a::va<int>&a::x<int>&a::y替换MEMBER_FUNCTION )。

clang compiles all of them. clang编译所有这些。
gcc compiles everything except (2) with &a::va<int> and &a::x<int> . gcc使用&a::va<int>&a::x<int>编译除(2)之外的所有内容。
MSVC compiles everything except (1) and (2) with &a::va<int> (but is fine with &a::x<int> ). MSVC使用&a::va<int>编译除(1)和(2)之外的所有内容(但是&a::x<int>可以很好)。

Notice that (3) is essentially the same as (4). 请注意,(3)与(4)基本相同。

https://gcc.godbolt.org/z/a2qqyo showing an example of this. https://gcc.godbolt.org/z/a2qqyo显示了这样的一个例子。

What you can see from this is that &MEMBER_FUNCTION is not resolved to a specific member function pointer in case of templates, but if it is resolved, it is allowed to reinterpret it into another member function pointer type. 您可以从中看到的是,在模板的情况下, &MEMBER_FUNCTION没有解析为特定的成员函数指针,但如果它被解析,则允许将其重新解释为另一个成员函数指针类型。

What the standard has to say: 标准有什么要说的:

[over.over]/1 : [over.over] / 1

A use of an overloaded function name without arguments is resolved in certain contexts to a function, a pointer to function or a pointer to member function for a specific function from the overload set. 在某些上下文中,使用不带参数的重载函数名称解析为函数,指向函数的指针或指向来自重载集的特定函数的成员函数的指针。 A function template name is considered to name a set of overloaded functions in such contexts. 函数模板名称被认为是在这种上下文中命名一组重载函数。 A function with type F is selected for the function type FT of the target type required in the context if F (after possibly applying the function pointer conversion) is identical to FT. 如果F(在可能应用函数指针转换之后)与FT相同,则为上下文中所需的目标类型的函数类型FT选择类型为F的函数。 [ Note: That is, the class of which the function is a member is ignored when matching a pointer-to-member-function type. [注意:也就是说,当匹配指向成员函数的指针类型时,忽略函数所属的类。 — end note ] The target can be: - 结束说明]目标可以是:
[...] [...]
- an explicit type conversion ([expr.type.conv], [expr.static.cast], [expr.cast]) - 显式类型转换([expr.type.conv],[expr.static.cast],[expr.cast])

An example given later on is: 稍后给出的一个例子是:

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

And a few more quotes about templates: 还有一些关于模板的引用:

[temp.deduct.funcaddr]/1 : [temp.deduct.funcaddr] / 1

Template arguments can be deduced from the type specified when taking the address of an overloaded function. 在获取重载函数的地址时,可以从指定的类型推导出模板参数。 The function template's function type and the specified type are used as the types of P and A, and the deduction is done as described in [temp.deduct.type]. 函数模板的函数类型和指定的类型用作P和A的类型,并且如[temp.deduct.type]中所述完成演绎。

[temp.arg.explicit]/4 [temp.arg.explicit / 4

[...] if a template argument list is specified and it, along with any default template arguments, identifies a single function template specialization, then the template-id is an lvalue for the function template specialization. [...]如果指定了模板参数列表,并且它与任何默认模板参数一起标识单个函数模板特化,那么template-id是函数模板特化的左值。

This seems like MSVC is right. 这看起来像MSVC是对的。

&a::va<int> is not resolved unless you assign/cast it to a void(a::*)(int) . 除非您将其分配/转换为void(a::*)(int)否则不会解析&a::va<int> You should also be able to assign it to void(a::*)(int, char) or void(a::*)(int, double, char) , where the Args would be deduced as { int, char } and { int, double, char } respectively. 您还应该能够将它分配给void(a::*)(int, char)void(a::*)(int, double, char) ,其中Args将被推导为{ int, char }{ int, double, char } That means that (no_args) &a::va<int> should fail, as there are many possible sets of Args it could be (All of them start with int , and clang overzealously resolves it), and none of them take zero parameters, so (no_args) &a::va<int> is a static_cast that should fail. 这意味着(no_args) &a::va<int> 应该失败,因为它可能有许多可能的Args集(所有这些都以int开头,而clang过分地解析它),并且它们都没有采用零参数, so (no_args) &a::va<int>是一个应该失败的static_cast

As for &a::x<int> , there is only one possible function, so it should work exactly the same way as &a::y (But gcc still hasn't resolved it yet). 至于&a::x<int> ,只有一个可能的函数,所以它应该和&a::y工作方式完全相同(但是gcc还没有解决它)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM