有没有比 allocator_type 的存在更好的方法来区分可调整大小的容器?

Is there a better way to distinguish resizable containers than presence of allocator_type?

我有 operator>>() 的模板重载,我需要区分可以调整大小的容器(例如 vector)和不能调整大小的容器(例如 array)。我目前只是在使用 allocator_type 特性(见下面的代码)——它工作得很好——但想知道是否有更明确的方法来测试它。

template <class T>
struct is_resizable {
    typedef uint8_t yes;
    typedef uint16_t no;

    template <class U>
    static yes test(class U::allocator_type *);

    template <class U>
    static no test(...);

    static const bool value = sizeof test<T>(0) == sizeof yes;
};

template <typename C>
typename boost::enable_if_c<
    boost::spirit::traits::is_container<C>::value && is_resizable<C>::value,
    istream &
>::type
operator>>(istream &ibs, C &c)
{
    c.resize(ibs.repeat() == 0 ? c.size() : ibs.repeat());
    for (typename C::iterator it = c.begin(); it != c.end(); ++it)
    {
        C::value_type v;
        ibs >> v;
        *it = v;
    }
    return ibs;
}

template <typename C>
typename boost::enable_if_c<
    boost::spirit::traits::is_container<C>::value && !is_resizable<C>::value,
    istream &
>::type
operator>>(istream &ibs, C &c)
{
    for (typename C::iterator it = c.begin(); it != c.end(); ++it)
        ibs >> *it;
    return ibs;
}

是的。您需要 define/use 自定义特征(例如 boost::spirit::traits 是)。

分配器的存在与否并不能真正告诉您容器是否是固定大小的。非标准容器可能根本没有 allocator_type 关联类型,但仍允许 resize(...)

事实上,由于您有效地断言了一个允许

概念
C::resize(size_t)

您可以为此使用表达式 SFINAE

如果您想测试容器是否支持 resize,您可能应该只检查它是否具有 resize() 功能。在 C++03 中,它看起来像:

template <typename T>
class has_resize
{
private:
    typedef char yes;
    struct no {
        char _[2];
    };

    template <typename U, U>
    class check
    { };

    template <typename C>
    static yes test(check<void (C::*)(size_t), &C::resize>*);

    template <typename C>
    static no test(...);

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

现代 C++ 有一种非常简洁的方法:

template <typename T, typename = int>
struct resizable : std::false_type {};

template <typename T>
struct resizable <T, decltype((void) std::declval<T>().resize(1), 0)> : std::true_type {};

Demo

现在如果你不需要区分名称为resize的成员函数和成员变量,你可以将上面的decltype写成如下:

decltype( (void) &T::resize, 0 )

请注意,转换为 void 是为了处理类型重载逗号运算符和泛化失败的情况(因此它比抱歉策略更安全)

感谢@Jarod42 在另一个问题上的帮助,我有一个适用于 C++98、C++03 和 C++11 的解决方案; g++ 和 VS2015。另外,对于问题儿童,std::vector<bool>.

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)                \
    template <typename U>                                                    \
    class traitsName                                                         \
    {                                                                        \
    private:                                                                 \
        typedef boost::uint8_t yes; typedef boost::uint16_t no;              \
        template<typename T, T> struct helper;                               \
        template<typename T> static yes check(helper<signature, &funcName>*);\
        template<typename T> static no check(...);                           \
    public:                                                                  \
        static const bool value = sizeof check<U>(0) == sizeof(yes);         \
    }

DEFINE_HAS_SIGNATURE(has_resize_1, T::resize, void (T::*)(typename T::size_type));
DEFINE_HAS_SIGNATURE(has_resize_2, T::resize, void (T::*)(typename T::size_type, \
    typename T::value_type));

下面是它的用法。请注意,resize()has_resize_1has_resize_2 成员函数签名均已检查。那是因为在 C++11 之前,resize() 有一个带有两个参数的签名,最后一个带有默认值;从 C++11 开始,它有两个签名——一个有一个参数,另一个有两个参数。此外,VS2015 显然具有 三个 签名——以上所有。解决方案就是始终检查两个签名。

可能有一种方法可以将两个检查组合成一个类型特征,例如has_resize<C>::value。知道的告诉我吧

template <typename T>
typename boost::enable_if_c<
    !boost::spirit::traits::is_container<T>::value,
    xstream &>::type
    operator>>(xstream &ibs, T &b)
{
    return ibs;
}

template <typename C>
typename boost::enable_if_c<
    boost::spirit::traits::is_container<C>::value &&
    (has_resize_1<C>::value || has_resize_2<C>::value),
    xstream &
>::type
operator>>(xstream &ibs, C &c)
{
    typename C::value_type v;
    ibs >> v;
    return ibs;
}

template <typename C>
typename boost::enable_if_c<
    boost::spirit::traits::is_container<C>::value &&
    !(has_resize_1<C>::value || has_resize_2<C>::value),
    xstream &
>::type
operator>>(xstream &ibs, C &c)
{
    typename C::value_type v;
    ibs >> v;
    return ibs;
}