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

时间:2012-11-26 10:16:11

标签: c++ templates visual-c++ gcc sfinae

我已经在问题中至少停留了两个星期,被我无法理解的东西所阻挡,并且在SO中提出的问题并没有真正指出真正的问题(愚蠢的我!)。最后,我希望我在这个问题上找到了我头痛的真正意义。

我一直在使用模板结构作为帮助程序来检测给定类型是否具有成员方法,此模板结构如下所示:

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

struct W的主要思想是将第四个参数作为成员函数指针放到我需要使用SFINAE技巧进行测试的成员函数中,in a previous question提出了一个替代方案,它帮助了我要理解我一直缺少的许多概念,但最终它并没有解决我的真正问题。

我正在处理的代码片段必须在Linux和Windows平台上运行,我用于Windows的编译器是MSVC(Visual Stuido 2010 10.0)和gcc(Devian 4.4.5-8)on Linux方面。问题是同一段代码不能在MSVC中编译,但它在gcc中编译(我感到震惊,通常是相反的,因为MSVC的标准严格程度较低)。

问题的根源是我的SFINAE方法在MSVC下编译时未能检测到方法set::insert,为了查找此失败,我写了simple test

#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符号替换期间set::insert检测失败的原因。错误文本cannot convert from 'overloaded-function'并没有提供任何有关正在发生的事情的线索(或者由于我的英语理解不佳而无法找到任何信息)。

乍一看,我一直认为问题是符号替换,但如果它在MSVC和gcc中都失败也会有意义。所以现在我想知道问题是否是某些MSVC编译器特定的做事方式。

任何关于为何在MSVC下失败以及如何使gcc和MSVC以相同方式工作的线索?

额外问题:std::mapstd::set提供了一个嵌套类型_Pairib作为红色黑树的MSVC实现下的::insert方法的返回类型(或者是mapset),gcc实现中没有等效的内容?

修改

在阅读chill answer后,我查看了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范围,但它属于公共范围,也是一种继承方法,因此为什么它不可访问在名称替换期间?

2 个答案:

答案 0 :(得分:2)

失败的原因是在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]”

  

对于指向成员函数的类型指针的非类型模板参数,   如果template-argument的类型为std :: nullptr_t,则为null成员   应用指针转换(4.11); 否则,不适用转化。   如果template-argument表示一组重载成员   函数,从集合中选择匹配成员函数   (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;

答案 1 :(得分:0)

根据this问题,标准没有定义您可以将成员函数指针带到标准对象的成员。

并且_Pairib是MSVC实现细节 - 当然你在GCC中找不到类似的东西。