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

542 views Asked by At

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

There are 4 answers

0
plong On BEST ANSWER

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;
}
0
sehe On

Yes. You need to define/use a custom trait (like boost::spirit::traits are).

The presence or absense of an allocator doesn't really tell you whether a container is fixed-size. Non-standard containers might not have an allocator_type associated type at all, whilst still allowing resize(...)

In fact, since you're effectively asserting a Concept that allows

C::resize(size_t)

you could just use expression SFINAE for that

3
Barry On

If you want to test is a container is resize-able, you should probably just check to see if it has a resize() function. In C++03, that would look like:

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));
};
5
Nikos Athanasiou On

Modern C++ has a really terse way :

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

Now if you don't need to disambiguate between member functions and member variables with the name resize, you could write the above decltype as follows :

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

note that the cast to void is done to handle cases where a type overloads the comma operator and generalizing fails (so it's just a better safe than sorry policy)