SFINAE 编译器问题

发布于 2024-10-06 02:04:44 字数 1245 浏览 3 评论 0原文

我的以下代码应该检测 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 的一个 bug?任何人都可以在不同的编译器上进行测试吗?谢谢!

The following code of mine should detect whether T has begin and end methods:

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)) };
};

And here is some test code:

#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';
}

On g++ 4.5.1, the output is 1 1 1 1. On Visual Studio 2008, however, the output is 1 1 0 0. Did I do something wrong, or is this simply a VS 2008 bug? Can anyone test on a different compiler? Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

枕梦 2024-10-13 02:04:44

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

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

在此步骤中,我能够实例化您的 sfinae 对象,但它仍然无法正常工作。 “这让我知道这是 VS 的一个 bug,所以接下来的问题是如何修复它。” -- 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' :无法从 'overloaded-function' 转换为 'std::_Tree_const_iterator<_Mytree>; (__thiscall std::set<_Kty>::* )(void) const'

这让我知道 &U::end 对于 VS 来说是不够的 (< em>任何编译器)能够告诉我想要哪个 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++ 应该会失败。

So, here's how I go about debugging these things.

First, comment out the negative alternative so you get an error instead of just a mismatch.
Next, try to instantiate the type you're putting in the function with one of the items that do not work.

At this step, I was able to instantiate your sfinae object but it still wasn't working. "This lets me know it IS a VS bug, so the question then is how to fix it." -- OBS

VS seems to have troubles with SFINAE when done the way you are. Of course it does! It works better when you wrap up your sfinae object. I did that like so:

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;
};

Still wasn't working, but at least I got a useful error message:

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

This lets me know that &U::end is not sufficient for VS (ANY compiler) to be able to tell which end() I want. A static_cast fixes that:

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

Put it all back together and run your test program on it...success with VS2010. You might find that a static_cast is actually all you need, but I left that to you to find out.

I suppose the real question now is, which compiler is right? My bet is on the one that was consistent: g++. Point to the wise: NEVER assume what I did back then.

Edit: Jeesh... You are wrong!

Corrected version:

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';
}

--
The debugging above is sensible, but the assumption about the compiler was wrong headed. G++ should have failed for the reason I emphasized above.

掀纱窥君容 2024-10-13 02:04:44

你为什么要付出这么多努力?如果您想检查 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)) };
};

Why are you going to all that effort? If you want to check if U::begin() exists, why not try it?

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)) };
};

In addition to checking for the existance of U::begin() and U::end(), this also checks whether they return something that is convertible to a const_iterator. It also avoids the pitfall highlighted by Stephan T. Lavavej by using a call expression that must be supported, instead of assuming a particular signature.

[edit]
Sorry, this relied on VC10's template instantiation. Better approach (puts the existance check in the argument types, which do participate in overloading):

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)) };
};
情丝乱 2024-10-13 02:04:44

使用 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 参数仅在容器同时提供两者时消除重载解析的歧义(或者如果 iteratortypedef const_iterator iterator,就像允许 std::set 一样) - 文字 0int 类型,并强制选择第一个重载。

With C++11, there are now better ways to detect this. Instead of relying on the signature of functions, we simply call them in an expression SFINAE context:

#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. The int and long parameters are only to disambiguate overload resolution when the container offers both (or if iterator is typedef const_iterator iterator, like std::set is allowed to) - literal 0 is of type int and forces the first overload to be chosen.

老娘不死你永远是小三 2024-10-13 02:04:44

Stephan T. Lavavej 有 this 表示:

请注意,从技术上讲,禁止获取标准库成员函数的地址。(它们可以重载,从而使 &foo::bar 不明确,并且它们可以有额外的默认参数,从而阻止通过 static_cast 消除歧义的尝试。)

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

Stephan T. Lavavej has this to say:

Please note that it is technically forbidden to take the address of a Standard Library member function. (They can be overloaded, making &foo::bar ambiguous, and they can have additional default arguments, defeating attempts to disambiguate via static_cast.)

So I guess I'm going to use the simpler version that only checks for the nested const_iterator type.

嘿哥们儿 2024-10-13 02:04:44

这可能应该是一条评论,但我没有足够的点

@MSalters

即使​​你的 is_container 工作(几乎)并且我自己使用了你的代码,我发现了其中的两个问题。

首先,类型 deque::iterator 被检测为容器(在 gcc-4.7 中)。看起来 deque::iterator 定义了 begin/end 成员和 const_iterator 类型。

第二个问题是,根据 GCC 开发人员的说法,该代码无效。我引用:默认参数的值不是函数类型的一部分,不参与推导。请参阅 GCC bug 51989

我目前正在使用 this(仅限 C++11)对于 is_container

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 };
};

This probably should be a comment, but I don't have enough points

@MSalters

Even though your is_container works (almost) and I've used your code myself, I've discovered two problems in it.

First is that type deque<T>::iterator is detected as a container (in gcc-4.7). It seems that deque<T>::iterator has begin/end members and const_iterator type defined.

2nd problem is that this code is invalid according to GCC devs. I qoute: values of default arguments are not part of the function type and do not take part in deduction. See GCC bug 51989

I am currently using this (C++11 only) for 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 };
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文