Search code examples
c++templatesboostcontainersenable-if

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


I have template overloads for operator>>() where I need to distinguish between containers that can be resized, e.g., vector, and containers that cannot, e.g., array. I am currently just using the presence of an allocator_type trait (see code, below)--and it works just fine--but was wondering if there is a more explicit way of testing this.

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

Solution

  • Thanks to help from @Jarod42 on a separate question, I have a solution that works with C++98, C++03, and C++11; g++ and VS2015. Also, for the problem child, 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));
    

    This is how it's used, below. Notice that both the has_resize_1 and has_resize_2 member-function signatures for resize() are checked. That's because before C++11, resize() had a single signature with two parameters, the last with a default value; as of C++11, it has two signatures--one with one parameter and the other with two parameters. Moreover, VS2015 apparently has three signatures--all of the above. The solution is just to check for both signatures all the time.

    There is probably a way to combine the two checks into a single type trait, such as has_resize<C>::value. Tell me if you know.

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