【问题标题】:SFINAE compiler troublesSFINAE 编译器问题
【发布时间】:2010-12-03 17:00:03
【问题描述】:

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

【问题讨论】:

  • 在 MinGW g++ 4.4.0 上为我工作(得到 1 1 1 1)。不幸的是,我不知道为什么它在 VS2008 上失败,尽管代码看起来是正确的。
  • 在 VS2010 1 1 0 0 上相同。我有一种预感,它可能正在调试 STL,所以我尝试了 /DDEBUG/DNDEBUG,但没有任何区别。
  • 您可以查看 Boost.MPL 提供的 HAS_XXX facility,了解它们如何解决某些编译器有限的 SFINAE 容量。
  • 另一个数据点:Comeau(使用 EDG,通常是一个良好的标准测试)似乎返回 1 1 1 1 - 您的程序在他们的在线试用中编译得很好-out comeaucomputing.com/tryitout 将长行注释掉。
  • @Rup:你真的可以在他们的试用网站上运行这个程序吗?

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


【解决方案1】:

所以,这就是我调试这些东西的方法。

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

在这一步,我能够实例化您的 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&lt;_Mytree&gt; (__thiscall std::set&lt;_Kty&gt;::* )(void) const'

这让我知道 &amp;U::end 不足以让 VS(ANY 编译器)知道我想要哪个 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++。 指出明智的:永远不要假设我当时的所作所为。

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

【讨论】:

  • @sbi @John @Rup 遗憾的是,此代码不起作用。如果该类型没有beginend 方法,则不会编译。
  • @Fred:但是您的原始代码也检查了begin()/end(),不是吗? (什么类会有嵌套的 const_iterator 类型,但没有 begin()/end() 返回它?)
  • @sbi:SFINAE 的意思是“替换失败不是错误”。如果T 没有beginend 方法,那么is_container&lt;T&gt;::value 仍应编译(并产生false)。这正是我的代码在 g++ 中正确执行的操作。
  • 问题是询问 MS 编译器的问题。我提供的解决方案确实按照 MSVC 中的要求工作。
【解决方案2】:

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

【讨论】:

  • 在 VS2005 上测试,即使 is_container&lt;std::set&lt;std::string&gt; &gt;::value 也能正常工作(是 true
  • typename U::const_iterator b = ((U*)0)-&gt;begin(),当我们填充默认参数时,它不会调用begin?,你能解释一下为什么begin((U*)0)-&gt;begin())没有被调用的概念吗? ,非常感谢:)
  • @Mr.Anubis:在调用函数的地方评估默认参数。由于test&lt;T&gt;() 实际上并未被调用(sizeof(expr) 不会计算表达式),因此也不会计算默认参数。但是,sizeof(test&lt;T&gt;(0)) 确实需要进行重载解析来确定返回类型。
  • 所以,当我们这样做 ((U*)0)-&gt;begin() 时,(甚至 test() 没有被调用)它只是检查非类型 arg b 的类型是否与((U*)0)-&gt;begin() 的表达式类型,对吧? .非常感谢:)
  • @Mr.Anubis:是的,而这又要求U 有一个成员begin,它回答了原始问题。
【解决方案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 iterator,例如允许 std::set) - 文字 0int 类型并强制选择第一个重载。

【讨论】:

    【解决方案4】:

    Stephan T. Lavavej 有 this 说:

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

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

    【讨论】:

      【解决方案5】:

      这应该是评论,但我的积分不够

      @MSalters

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

      首先,deque&lt;T&gt;::iterator 类型被检测为容器(在 gcc-4.7 中)。似乎deque&lt;T&gt;::iterator 定义了begin/end 成员和const_iterator 类型。

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

      我目前正在为is_container&lt;T&gt; 使用this(仅限C++11):

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

      【讨论】:

      • 那么deque 不是容器吗?
      • deque 是一个容器,但 deque&lt;T&gt;::iterator 不是。
      • 第二个问题是真实存在的。我忽略了 14.7.1/2;默认参数不使用模板本身实例化。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-08
      相关资源
      最近更新 更多