指向模板参数中重载函数的MSVC指针

MSVC pointer to overloaded function in template parameter

本文关键字:函数 MSVC 指针 重载 参数      更新时间:2023-10-16

我已经在一个问题上徘徊了至少两周,被我无法理解的东西挡住了去路,并在SO中提出了一些并没有真正指向真正问题的问题(我真傻!)。最后,我希望我能在这个问题上找到我头疼的真正原因。

我一直在使用一个模板结构作为助手来检测给定类型是否有成员方法,这个模板结构看起来像这样:

template
<
typename Type,
typename Return,
typename Parameter,
Return (Type::*)(Parameter)
> struct W {};

struct W的主要想法是将一个成员函数指针作为第四个参数,指向我需要使用SFINAE技巧测试的成员函数。在之前的一个问题中,有人提出了一个替代方案,它帮助我理解了许多我遗漏的概念,但最终,它并不能解决我的真正问题。

我正在处理的这段代码必须在Linux和Windows平台上都能工作,我在Windows上使用的编译器是Linux端的MSVC(Visual Stuido 2010 10.0)和gcc(Devian 4.4.5-8)。问题是,同一段代码不是在MSVC中编译的,而是在gcc中编译的(我很震惊,通常是opsite,因为MSVC的标准不那么严格)。

问题的根源是,我的SFINAE方法在MSVC下编译时未能检测到方法set::insert,为了寻找这个失败,我写了一个简单的测试:

#include <set>
int main(int argc, char **argv)
{
typedef std::set<int> setint;
std::pair<setint::iterator, bool> (setint::*p_1)(const setint::value_type &) = &setint::insert;
W<setint, std::pair<setint::iterator, bool>, const setint::value_type &, &setint::insert> w_1;
return 0;
}

正如我之前提到的:这个示例使用gcc编译没有问题,但在使用MSVC时,在w_1:的声明中出现了错误

error C2440: 'specialization' : cannot convert from 'overloaded-function' to 'std::pair<_Ty1,_Ty2> (__thiscall std::set<_Kty>::* )(const int &)'
with
[
_Ty1=std::_Tree_const_iterator<std::_Tree_val<std::_Tset_traits<int,std::less<int>,std::allocator<int>,false>>>,
_Ty2=bool,
_Kty=int
]
None of the functions with this name in scope match the target type

在创建函数指针时,可以按预期工作,因此p_1的声明会编译;但是具有与模板参数相同的签名却没有。这就是为什么在SFINAE的符号替换期间CCD_ 5检测失败的原因。错误文本cannot convert from 'overloaded-function'没有为我提供任何关于发生了什么的线索(或者由于我的英语理解能力差,我找不到任何线索)。

乍一看,我一直认为问题在于符号替换,但如果它在MSVC和gcc中都失败了,那就有意义了。所以现在我想知道问题是否是MSVC编译器特定的方法。

关于它为什么在MSVC下失败,以及如何使gcc和MSVC以相同的方式工作,有什么线索吗

额外的问题:std::mapstd::set在红黑树的MSVC实现下(或者mapset下的任何东西)为::insert方法提供了嵌套类型_Pairib作为返回类型,在gcc实现中没有等价的?

编辑:

在阅读了冷静的答案之后,我研究了MSVC的std::set实现。

std::setstd::_Tree的派生类,它提供了我想要检查的方法:

// class std::_Tree, file xtree in the path 'VisualStudioPath/VC/include/'
_Pairib insert(const value_type& _Val)
{    // try to insert node with value _Val, favoring right side
return (insert(_Val, false));
}

这个插入方法属于std::_Tree作用域,而不是std::set作用域,但它属于公共作用域,也是一个继承的方法,所以为什么在名称替换过程中它不可访问

失败的原因是在VC stdlib中,insert实际上不是set类模板本身的成员,而是继承的:

测试用例,

template
<
typename Type,
typename Return,
typename Parameter,
Return (Type::*)(Parameter)
> struct W {};
struct A { void foo (int); };
struct B : public A {};
int main(int argc, char **argv)
{
void (B::*p)(int) = &B::foo;
// W<B, void, int, &B::foo> w; // error
W<A, void, int, &A::foo> w;
return 0;
}

PS。该错误在某些GCC和某些MSVC中都是错误。

但它在公共范围内,也是一个继承的方法,所以我不明白为什么它不可访问。

因为在这种情况下不允许转换,"14.3.2。模板非类型参数[#5]">

对于指向成员函数的指针类型的非类型模板参数,如果模板参数的类型为std::nullptr_t,则null成员应用指针转换(4.11)否则,不应用任何转换。如果模板参数表示一组重载成员函数,从集合中选择匹配的成员函数(13.4).

PPS。所以,你显式地进行转换,瞧,你不需要关心可能的基类:

W<setint,
std::pair<setint::iterator, bool>,
const setint::value_type &,
static_cast<std::pair<setint::iterator, bool> (setint::*)(const setint::value_type &)> (&setint::insert)> w_1;

根据这个问题,Standard没有定义您可以将成员函数指针指向Standard对象的成员。

_Pairib是一个MSVC实现细节——当然在GCC中找不到类似的东西。