7

我的以下代码应该检测是否Tbeginend方法:

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 错误?任何人都可以在不同的编译器上进行测试吗?谢谢!

4

5 回答 5

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'

这让我知道VS(任何编译器)能够分辨出我想要哪个 end()&U::end是不够的。 一个 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++。指向智者:永远不要假设我当时做了什么。

编辑:天哪……你错了!

修正版:

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++ 应该失败了。

于 2010-12-03T17:56:05.737 回答
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)) };
};

除了检查 and 的存在之外U::begin()U::end()这还检查它们是否返回可转换为 a 的东西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)) };
};
于 2010-12-10T15:21:40.247 回答
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;
};

Ideone 上的实时示例。intlong参数仅在容器同时提供两者时(或者如果iteratoris typedef const_iterator iterator, likestd::set被允许)消除重载决议的歧义 - 文字是0类型int并强制选择第一个重载。

于 2012-02-08T16:03:46.233 回答
1

Stephan T. Lavavej 有这样的说法:

请注意,技术上禁止获取标准库成员函数的地址。(它们可能会被重载,变得&foo::bar模棱两可,并且它们可以有额外的默认参数,从而阻止通过 .) 消除歧义的尝试static_cast。)

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

于 2010-12-10T13:25:09.080 回答
1

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

@MSalters

即使您的is_container作品(几乎)并且我自己也使用过您的代码,但我发现其中有两个问题。

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

第二个问题是,根据 GCC 开发人员,此代码无效。I qoute:默认参数的值不是函数类型的一部分,也不参与 deduction。请参阅GCC 错误 51989

我目前正在将此(仅限 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 };
};
于 2012-02-08T15:19:14.493 回答