SFINAE编译器麻烦

时间:2010-12-03 17:00:04

标签: c++ visual-studio-2008 stl containers sfinae

我的以下代码应检测T是否有beginend方法:

template <typename T>
struct is_container
{
    template <typename U, typename U::const_iterator (U::*)() const,
                          typename U::const_iterator (U::*)() const>
    struct sfinae {};

    template <typename U> static char test(sfinae<U, &U::begin, &U::end>*);
    template <typename U> static long test(...);

    enum { value = (1 == sizeof test<T>(0)) };
};

以下是一些测试代码:

#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <map>

int main()
{
    std::cout << is_container<std::vector<std::string> >::value << ' ';
    std::cout << is_container<std::list<std::string> >::value << ' ';
    std::cout << is_container<std::set<std::string> >::value << ' ';
    std::cout << is_container<std::map<std::string, std::string> >::value << '\n';
}

在g ++ 4.5.1上,输出为1 1 1 1。但是,在Visual Studio 2008上,输出为1 1 0 0。我做错了什么,或者这只是一个VS 2008错误?任何人都可以测试不同的编译器吗?谢谢!

5 个答案:

答案 0 :(得分:12)

所以,这就是我如何调试这些东西。

首先,注释掉否定的替代方案,这样你就会得到错误,而不仅仅是错配。 接下来,尝试使用其中一个不起作用的项来实例化您放入函数的类型。

在这一步,我能够实例化你的sfinae对象,但它仍然无法正常工作。 “这让我知道它是一个VS错误,所以问题就是如何修复它。” - OBS

当你按照自己的方式完成时,VS似乎对SFINAE有麻烦。 当然可以!当你包装你的sfinae对象时它会更好。我是这样做的:

template <typename U, typename it_t = typename U::const_iterator >
struct sfinae 
{
  // typedef typename U::const_iterator it_t; - fails to compile with non-cont types.  Not sfinae
  template < typename U, typename IT, IT (U::*)() const, IT (U::*)() const >
  struct type_ {};

  typedef type_<U,it_t,&U::begin,&U::end> type;
};

仍然无法正常工作,但至少我收到了一条有用的错误消息:

error C2440: 'specialization' : cannot convert from 'overloaded-function' to 'std::_Tree_const_iterator<_Mytree> (__thiscall std::set<_Kty>::* )(void) const'

这让我知道&U::end不足以让VS( ANY 编译器)能够告诉我想要哪一端() static_cast修复了

  typedef type_<U,it_t,static_cast<it_t (U::*)() const>(&U::begin),static_cast<it_t (U::*)() const>(&U::end)> type;

全部重新组合并运行测试程序......使用VS2010取得成功。你可能会发现static_cast实际上就是你所需要的,但是我把它留给你去找。

我想现在真正的问题是,哪个编译器是正确的?我的赌注是一致的:g ++。 指向聪明人:从不假设我当时做了什么。

编辑:Jeesh ...... 你错了!

更正版本:

template <typename T>
struct is_container
{
    template <typename U, typename it_t = typename U::const_iterator > 
    struct sfinae 
    {
      //typedef typename U::const_iterator it_t;
      template < typename U, typename IT, IT (U::*)() const, IT (U::*)() const >
      struct type_ {};

      typedef type_<U,it_t,static_cast<it_t (U::*)() const>(&U::begin),static_cast<it_t (U::*)() const>(&U::end)> type;
    };

    template <typename U> static char test(typename sfinae<U>::type*);
    template <typename U> static long test(...);

    enum { value = (1 == sizeof test<T>(0)) };
};



#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <map>

int main()
{
    std::cout << is_container<std::vector<std::string> >::value << ' ';
    std::cout << is_container<std::list<std::string> >::value << ' ';
    std::cout << is_container<std::set<std::string> >::value << ' ';
    std::cout << is_container<std::map<std::string, std::string> >::value << ' ';
    std::cout << is_container<bool>::value << '\n';
}

- 上面的调试是明智的,但关于编译器的假设是错误的。由于我上面强调的原因,G ++应该失败了。

答案 1 :(得分:5)

你为什么要这么努力?如果您想检查U::begin()是否存在,为什么不试试?

template <typename T>
struct is_container
{
    template <typename U> static char test(U* u,
       typename U::const_iterator b = ((U*)0)->begin(),
       typename U::const_iterator e = ((U*)0)->end());
    template <typename U> static long test(...);

    enum { value = (1 == sizeof test<T>(0)) };
};

除了检查U::begin()U::end()的存在外,还会检查它们是否返回可转换为const_iterator的内容。它还避免了Stephan T. Lavavej通过使用必须支持的调用表达式突出显示的陷阱,而不是假定特定的签名。

[编辑] 对不起,这依赖于VC10的模板实例化。更好的方法(将存在性检查放在参数类型中,参与重载):

template <typename T> struct is_container
{
    // Is.
    template <typename U>
    static char test(U* u, 
                     int (*b)[sizeof(typename U::const_iterator()==((U*)0)->begin())] = 0,
                     int (*e)[sizeof(typename U::const_iterator()==((U*)0)->end())] = 0);
    // Is not.
    template <typename U> static long test(...);

    enum { value = (1 == sizeof test<T>(0)) };
};

答案 2 :(得分:3)

使用C ++ 11,现在有更好的方法来检测它。我们只是在表达式SFINAE上下文中调用它们,而不是依赖于函数的签名:

#include <type_traits> // declval

template<class T>
class is_container{
  typedef char (&two)[2];

  template<class U> // non-const
  static auto test(typename U::iterator*, int)
      -> decltype(std::declval<U>().begin(), char());

  template<class U> // const
  static auto test(typename U::const_iterator*, long)
      -> decltype(std::declval<U const>().begin(), char());

  template<class>
  static two  test(...);

public:
  static bool const value = sizeof(test<T>(0, 0)) == 1;
};

Live example on Ideone. intlong参数仅用于在容器同时提供(或iteratortypedef const_iterator iteratorstd::set消除重载歧义,例如{{ 1}}允许) - 文字0的类型为int,并强制选择第一个重载。

答案 3 :(得分:1)

Stephan T. Lavavej this说:

  

请注意,技术上禁止使用标准库成员函数的地址。(它们可能会重载,使&foo::bar不明确,并且可以有其他默认参数,通过static_cast打败消除歧义的企图。)

所以我想我将使用更简单的版本来检查嵌套的const_iterator类型。

答案 4 :(得分:1)

这可能应该是评论,但我没有足够的分数

@MSalters

即使你的is_container工作(差不多)而且我自己也使用了你的代码,我发现其中有两个问题。

首先,类型deque<T>::iterator被检测为容器(在gcc-4.7中)。似乎deque<T>::iterator已定义begin / end个成员和const_iterator类型。

第二个问题是根据GCC开发人员,此代码无效。我qoute:默认参数的值不是函数类型的一部分,不参与演绎。见GCC bug 51989

我目前正在使用this(仅限C ++ 11)is_container<T>

template <typename T>
struct is_container {
    template <
        typename U,
        typename S = decltype (((U*)0)->size()),
        typename I = typename U::const_iterator
    >
    static char test(U* u);
    template <typename U> static long test(...);
    enum { value = sizeof test<T>(0) == 1 };
};