【问题标题】:convert member function to pointer to member function将成员函数转换为指向成员函数的指针
【发布时间】:2019-08-05 15:17:33
【问题描述】:

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

【问题讨论】:

  • 在您的问题中包含编译器错误。
  • 无法转换函数。您是指来自 gcc.godbolt 的完整日志吗?
  • 它不需要是完整的日志,但包括编译器错误的错误消息(和以下信息行)。
  • F 是一个指向不带参数的成员函数的指针。 va&lt;int&gt; 接受一个参数。 X&lt;int&gt; 也是如此。因为什么时候可以将指向带有一个参数的函数的指针转换为指向另一个不带参数的函数的指针,无论它们是常规函数还是类方法?这种转换不应该起作用。 y 也接受一个 int 参数。这种转换也不应该起作用。但是在那里有一个明确的演员呢......
  • 为什么不应该是 UB?如果将带参数的函数转换为不带参数的函数是否合法,我也很感兴趣。也将寻找答案)

标签: c++ templates


【解决方案1】:

testxtesty 格式正确,所以 gcc 对 testx 的看法是错误的。但标准对testva 有点模糊。

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

对于模板函数,我们有[over.over]/1-2:

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

  • ...

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

  • ...

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

所以这意味着我们首先尝试对a&lt;b&gt;::x&lt;int&gt; 进行模板参数推导,将其与目标类型void (a&lt;b&gt;::*)() 匹配。但是没有任何特化可以给出完全匹配,因为它们都有一个参数,而不是零,所以推论失败了。但根据注释,还有 [temp.arg.explicit](C++17 中的第 3 段,4 in the latest C++20 draft):

可以从显式模板参数列表中省略可以从默认模板参数推导出或获得的尾随模板参数。没有以其他方式推导的尾随模板参数包([temp.variadic])将被推导为模板参数的空序列。 ...在演绎完成但失败的上下文中,或在未完成演绎的上下文中,如果指定了模板参数列表,并且它与任何默认模板参数一起标识单个函数模板特化,则 template-id 是函数模板特化的左值。

testx 中,template-id a&lt;b&gt;::x&lt;int&gt; 标识单个函数模板特化。因此它命名了该专业化,并且 C 风格的转换同样有效,结果未指定。

那么在testva 中,a&lt;b&gt;::va&lt;int&gt; 是否确定了一个专业化?当然可以通过[temp.arg.explicit]/9 使用该表达式来命名不同的专业:

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

除了这个说“模板参数推导”。这里涉及的模板参数推导失败了,因为它需要与目标类型void (a&lt;b&gt;::*)() 不可能匹配。因此,没有什么能真正解释 a&lt;b&gt;::va&lt;int&gt; 是否标识了单个特化,因为没有描述其他获取额外模板参数的方法,或者标识了多个特化,因为它可以在具有匹配目标类型的其他上下文中有效使用。

【讨论】:

  • @YSC 哎呀,好消息。虽然可能还有其他措辞说template &lt;class X, class Y&gt; void f(X, Y); int g(void (*)(int, int)); int a = g(f&lt;int&gt;); 应该有效?
  • 我很困惑。如果您的解释是正确的,gcc.godbolt.org/z/YNczD2 是否有效?
  • @YSC 虽然我猜如果 [over.over]/1-2 也不适用于 template-id,那么 Note 就没有意义了。我对va 案子还没有决定,这个例子基本上有相同的问题。不过,有趣的是 gcc 和 msvc 切换到允许它。
【解决方案2】:

叮当是对的

[expr.reinterpret.cast]/10

如果T1,“指向T1 类型的X 成员的指针”类型的纯右值可以显式转换为不同类型的“指向T2 类型的Y 成员的指针”类型的纯右值和T2 都是函数类型或都是对象类型。空成员指针值转换为目标类型的空成员指针值。此转换的结果未指定,但以下情况除外:

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

&amp;a&lt;b&gt;::va&lt;int&gt; et al.prvalue 类型的“指向a&lt;b&gt; 类型void(int) 的成员的指针”并将其转换(不调用结果函数值未指定的指针)是合法的。

【讨论】:

  • 该子句描述了reinterpret_cast。我在问题中没有看到reinterpret_cast
  • @Sam 因为没有其他有效的演员表,(F) 默认为重新解释演员表。
  • “因为没有其他演员是有效的” IMO 一个完整的答案将从证明这一点开始。也许这就是差异所在
  • @Light 10 分钟前我还在办公室;)
  • 我期待它的其余部分:P
【解决方案3】:

让我们简化为这个例子:

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 替换为&amp;a::va&lt;int&gt;&amp;a::x&lt;int&gt;&amp;a::y)。

clang 编译所有这些。
gcc 使用 &amp;a::va&lt;int&gt;&amp;a::x&lt;int&gt; 编译除 (2) 之外的所有内容。
MSVC 使用 &amp;a::va&lt;int&gt; 编译除 (1) 和 (2) 之外的所有内容(但使用 &amp;a::x&lt;int&gt; 很好)。

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

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

从中可以看出&amp;MEMBER_FUNCTION在模板的情况下并没有被解析为特定的成员函数指针,但是如果被解析了,则允许将其重新解释为另一个成员函数指针类型。

标准是怎么说的:

[over.over]/1:

使用不带参数的重载函数名称在某些上下文中被解析为函数、函数指针或重载集中特定函数的成员函数指针。 函数模板名称被认为是在此类上下文中命名一组重载函数。 如果 F(在可能应用函数指针转换之后)与 FT 相同,则选择具有 F 类型的函数作为上下文中所需的目标类型的函数类型 FT。 [ 注意:即在匹配指向成员函数的指针类型时,将忽略该函数所属的类。 —— 尾注 ] 目标可以是:
[...]
- 显式类型转换([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

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

这似乎 MSVC 是对的。

&amp;a::va&lt;int&gt; 不会被解析,除非您将其分配/强制转换为 void(a::*)(int)。您还应该能够将其分配给void(a::*)(int, char)void(a::*)(int, double, char),其中Args 将分别推导出为{ int, char }{ int, double, char }。这意味着(no_args) &amp;a::va&lt;int&gt; 应该 失败,因为有很多可能的Args 集合(它们都以int 开头,并且clang 过分热心地解决了它),并且没有一个取零参数,所以(no_args) &amp;a::va&lt;int&gt; 是一个应该失败的static_cast

至于&amp;a::x&lt;int&gt;,只有一个可能的功能,所以它的工作方式应该和&amp;a::y完全一样(但gcc还没有解决它)。

【讨论】:

  • 你确定吗?任何带有指定模板参数的可变参数模板是否至少意味着这些参数? va 的意思是简单的 va 与 Args = int 仅此而已?
  • @NN_ 您可以指定一些模板参数,其余的将被推导(这就是为什么 templated_function&lt;&gt; 如果将其分配给指针将起作用,并且参数的其余部分pack 不能被推断为空,以防它被分配给它不是的指针)。 Example.
  • 我明白了。现在的问题是为什么只有 clang 允许将可变参数模板函数转换为不带参数的函数。 godbolt.org/z/Racc05 void test() { 使用 F = void(*)();使用 MF = void(a::*)(); F f = (F)freefunc; MF mf = (MF)&a::memfunc; }
  • @NN_ 我假设在演员阵容中,clang 错误地解析了freefunc&lt;int&gt;(如here 所见,它创建了void freefunc(int);)。另外两个不这样做,因此他们尝试将未解析的函数解析为没有参数的函数(失败)。 clang 编译器,当调用reinterpret_casttries to resolve templates,但是(续)
  • @NN_ 也在那里完全解析参数包,这就是为什么它仍然可以分配更长的参数包,但也可以强制转换。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-22
  • 1970-01-01
相关资源
最近更新 更多