【问题标题】:is_container trait fails on std::set SFINAE issueis_container trait 在 std::set SFINAE 问题上失败
【发布时间】:2012-02-11 17:13:57
【问题描述】:

我正在尝试为 std 容器编写流操作符,主要用于调试目的。

我有以下代码:

#include <type_traits>
#include <iostream>
#include <ostream>
#include <iterator>
#include <algorithm>
#include <functional>

#include <vector>
#include <set>
#include <deque>


template<typename Container>
struct is_container
{
    typedef char no;
    typedef long yes;


    template<typename A, A, A>
    struct is_of_type;

    template<typename T>
            static yes& is_cont(
                            is_of_type
                            <
                                    typename T::iterator(T::*)(), 
                                    &T::begin,
                                    &T::end
                            >*);


    template<typename T>
    static no& is_cont(...);        //any other

    template<typename C, bool B>
    struct is_class_is_container
    {
            const static bool value=sizeof( is_cont<C>(nullptr) )==sizeof(yes);
    };

    template<typename C>
    struct is_class_is_container<C, false>
    {
            const static bool value=false;
    };

    const static bool value = is_class_is_container
            <
                    Container, 
                    std::is_class<Container>::value 
            >::value;
};

template<typename T>
    typename std::enable_if
    < is_container<T>::value, std::ostream >::type& 
    operator<<(std::ostream& os, const T& a)
{
    os << '[';
    std::copy(a.begin(), a.end(), std::ostream_iterator<typename T::value_type>(os, ", "));
    os << ']';
    return os;
}

我知道这远非完美(赞赏建设性的 cmets),但我遇到的问题是它适用于向量、双端队列和列表,但无法在集合上匹配,我不知道为什么,因为集合仍然有迭代器接口开始和结束。

谢谢。

编辑:测试 g++ (GCC) 4.6.2 2012012 铿锵3.0版

EDIT2:我使用 decltype 得到了它的工作方式,但是这是次优的,因为现在我不能断言它符合我的期望(返回一个迭代器)。

我一开始并不完全知道该集合返回了什么,也许如果有人有调试 TMP 的方法会很好。

【问题讨论】:

  • 如果您有兴趣,我在pretty printer 中有某种形式的is_container 特征。
  • @KerrekSB 谢谢,我会看一下,但是因为这真的是对我是否能得到这样的东西的考验,所以我想这样做并尝试让它工作。
  • 这看起来没有必要。试试这个:ideone.com/gBx6P
  • @JohannesSchaub-litb:是的,这还是好多了,我不知道你可以用 type_traits 进行继承,它更干净。我只需要阻止它绑定已经定义了运算符的情况。
  • 可能是因为std::set 不是通常意义上的容器。 (它违反了您期望从容器中获得的某些属性。)也许std::set 类似于不可变容器(不能修改元素)。

标签: c++ templates c++11 sfinae typetraits


【解决方案1】:

由于std::set&lt;T&gt; 只有一组不可变迭代器,所以只有begin()end() 的一个版本被声明为const。也就是说,std::set&lt;T&gt; 的定义看起来像这样(假设它之前在命名空间 std 中声明过):

template <typename T>
class std::set
{
public:
    class            iterator;
    typedef iterator const_iterator;
    ...
    const_iterator begin() const;
    const_iterator end() const;
    ...
};

其他容器同时具有const 和非const 版本的begin()end() 匹配您要求的签名。 std::set 没有这个。不过,我不确定最简单的解决方法是什么。

也就是说,sizeof(char) 可以是sizeof(long)。保证yesno 类型具有不同大小的最简单方法是对同一类型使用不同大小的数组的引用,例如:

typedef char (&yes)[1];
typedef char (&no)[2];
...
enum { value = sizeof(some_expression) == sizeof(yes) };

【讨论】:

  • 我当时很接近,我尝试了数组的东西,但没有意识到它必须是一个参考。那比较好。我也不知道 set 只有 const 迭代器。我想我被这个扔了:en.cppreference.com/w/cpp/container/set/begin 哦,它现在可以工作了。我真的不认为我会打扰我针对 c++11 的枚举位,所以我不认为 vc6.0 的怪癖是混淆我的代码的充分理由。无论如何,再次感谢。
  • 嗯,enum 位对于“VC6.0”来说并不是一个怪癖,但如果你最终将值传递给需要定义值的东西,例如一个由const&amp; 获取的函数:您的static bool const value = ... 仍然是不是定义,尽管它被赋予了一个初始值!如果您使用的是 C++2011,您还可以使用 static bool constexpr value = ... 定义(不仅仅是初始化)类定义中的值(尽管我不完全确定 constexpr 限定符可以去哪里,即这个符号可能是略有错误)。
  • 其实现在想想,consexpr 可以撕掉yes/no
  • @Dietmar 你是说类定义中的static bool constexpr value = ... 是一个定义,而不仅仅是一个声明?那对我来说是新的。我在哪里可以读到它?
  • @JohannesSchaub-litb:不,我并不是说类定义中的constexpr 是一个定义。它仍然是一个声明。但是,我的理解是,将此值绑定到 const&amp; 不需要定义可用,而对在类定义中初始化的 static T const 执行相同操作确实需要定义可用。
【解决方案2】:

它适用于vector,但不适用于set,因为后者为begin/end 函数返回const_iterator。变化:

typename T::iterator(T::*)(), 

到:

typename T::const_iterator(T::*)() const, 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-19
    • 1970-01-01
    • 2011-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-02
    • 1970-01-01
    相关资源
    最近更新 更多