MSVC:推理指向具有常量和非常量重载的成员函数的指针

MSVC: inference of pointer to member function with const and non-const overloads

本文关键字:常量 重载 成员 函数 指针 非常 MSVC 推理      更新时间:2023-10-16

这是一个最小的示例,其中 MSVC 无法决定是使用成员函数的 const 版本还是非 const 版本:

struct A {
int b() const;
int& b();
};
template <typename T, typename Ref>
void set(A&, Ref (A::*)(), T);
int main() {
auto a = A{};
set(a, &A::b, 123);
}

错误消息是

错误 C2783:"无效集(A &,引用 (__cdecl A::* )(无效),T)":无法推断出"Ref"的模板参数

GCC 和 Clang 更喜欢 non-const 方法,并且编译它没有问题。它们需要Ref (A::*)() const来选择常量版本。

有没有办法将MSVC推向正确的方向?

https://godbolt.org/z/ejT-Ls

您可以通过强制转换函数指针来强制选择非 const 版本:

set(a, static_cast<int & (A::*)()>(&A::b), 123);

我相信这一定是MSVC中的一个错误。您有权调用函数模板,但需扣除模板参数。一个参数是指向成员函数类型的指针,因此 [temp.deduct.call]/6 适用:

P是函数类型、函数指针类型或指向成员函数类型的指针时:

  • 如果参数是包含一个或多个函数模板的重载集,则该参数将被视为非推导上下文。
  • 如果参数是重载集(不包含函数模板),则尝试使用该集的每个成员进行试验参数推导。如果只有一个重载集成员的扣除成功,则该成员将用作扣除的参数值。如果重载集的多个成员的演绎成功,则参数将被视为非推导上下文。

在您的情况下,参数是不包含任何函数模板的重载集。因此,通过 [temp.deduct.call]/6.2,编译器应该尝试使用每个方法进行参数推导。参数推导应该只使用一种方法(非常量方法)成功,然后应该是为函数调用选择的方法。

@SoronelHaetir已经在他的回答中发布了一个解决方法。我建议您提交错误报告...

完成这项工作的另一种方法是切换set的模板参数,并用decltype指定第一个:

template <typename Ref, typename T>
void set(A&, Ref (A::*)(), T);
set<decltype(A{}.b())>(a, &A::b, 123);

对于不影响调用方的解决方法,可以推断类类型,并且仅在推导类型为 A 时启用该函数。

#include <type_traits>
struct A {
int b() const;
int& b();
};
template <typename T, typename Ref, typename DeducedA,
typename std::enable_if<std::is_same<DeducedA, A>::value, int>::type = 0>
void set(A&, Ref (DeducedA::*)(), T);
int main() {
auto a = A{};
set(a, &A::b, 123);
}

https://godbolt.org/z/GPxb9xaen