Question

Is there a "sufficiently" reliable way to detect an allocator in a template parameter. That is, I need something like a is_allocator type trait which can be used in an enable_if:

Suppose there is a class template future (with template parameter T):

    // Default ctor with allocator
    template <class Alloc, class... Args
        class Enable = typename std::enable_if<
            is_allocator<Alloc>::value
            and std::is_constructible<T, Args...>::value
        >::type
    >
    future(const Alloc& a, Args&&... args)
    : _shared_value(std::allocate_shared<T>(a, std::forward<T>(args...))
    {
    }



    // Default ctor (without allocator)
    template <class... Args
        class Enable = typename std::enable_if<
            std::is_constructible<T, Args...>::value
        >::type
    >
    future(Args&&... args)
    : _shared_value(std::make_shared<T>(std::forward<T>(args...))
    {
    }

Here, _shared_value is a std::shared_pointer<T>.

No correct solution

OTHER TIPS

There is no such is_allocator trait in the Standard Library, but you can write one yourself:

#include <vector>
#include <utility>

template <class T>
class is_allocator
{
    typedef char yes;
    typedef long no;

    // you can extend this with many more checks on the allocator interface
    template <class C> static yes check( decltype(std::declval<C>().allocate(0)) );

    template <class C> static no  check(...);
public:
    enum { value = sizeof(check<T>(0)) == sizeof(yes) };
};

int main()
{
    std::vector<int> v { 1, 2 };
    using V = decltype(v)::value_type;
    using A = decltype(v)::allocator_type;
    static_assert(!is_allocator<V>::value, "");
    static_assert( is_allocator<A>::value, "");
}

Live Example.

The above code checks whether a type has a member function allocate(size_type) by calling that function inside a decltype() expression. If such a function exists, the check<T>(0) will select that overload in the enum expression and the value will become true. As a check, you can static_assert this on the template parameters of a std::vector.

Obviously, you could improve this approach by having a bunch of fine-grained traits has_allocate, has_deallocate and all the other essential member function that make up the entire Allocator requirements in the Standard. Once you have done that, you can define is_allocator as the logical and over all these fine-grained traits.

Well, after getting very helpful answers and comments from @TemplateRex and @Casey, I finally came up with the following improved solution:

The helper class template is_allocator checks whether the given template argument (the potentially Allocator) has an embedded type value_type, whether it implements a member allocate(n) and whether it implements deallocate(ptr, n), where ptr is of type result_type of allocate.

A few requirements are still not checked, as you can see here: Requirements

(Edited): applying further improvements after @Casey's comments:

template <class T>
struct __has_allocate
{
private:
    template <class U> static std::false_type test(...);
    template <class U> static std::true_type test(decltype(std::declval<U>().allocate(0)));
public:
    enum { value = decltype(test<T>(0))::value };
};


template <class T>
struct __has_value_type
{
private:
    template <class U> static std::false_type test(...);
    template <class U> static std::true_type test(typename U::value_type*);
public:
    enum { value = decltype(test<T>(0))::value };
};


template <class T, bool HasAllocate = has_allocate<T>::value>
struct __has_deallocate
{
private:

    typedef decltype(std::declval<T>().allocate(0)) pointer;

    template <class Alloc, class Pointer>
    static auto
    test(Alloc&& a, Pointer&& p)
    -> decltype(a.deallocate(p,0), std::true_type());

    template <class Alloc, class Pointer>
    static auto
    test(const Alloc& a, Pointer&& p)
    -> std::false_type;

public:
    enum { value = decltype(test<T>(std::declval<T>(), std::declval<pointer>()))::value };
};


template <class T>
struct __has_deallocate<T, false>
{
    enum { value = false };
};




template <class T>
struct is_allocator
{
    enum { value =  __has_value_type<T>::value
                and __has_allocate<T>::value
                and __has_deallocate<T>::value
    };
};



// Test:

static_assert(is_allocator<int>::value == false, "");
static_assert(is_allocator<std::allocator<int>>::value == true, "");

There is a Boost proposal here: https://tickcpp.readthedocs.io/en/latest/tick/traits/is_allocator.html

My personal interpretation can be translated into this detection idiom. (Although, as usual with concepts, I don't know if this under/over constraining the concept.)

#include<utility> // declval

template<
    class A, 
    class P = typename A::pointer, class S = typename A::size_type,
    typename = decltype( 
        std::declval<A const&>()==A{std::declval<A const&>()},
        std::declval<A&>().deallocate(P{std::declval<A&>().allocate(std::declval<S>())}, std::declval<S>())
    )
>
std::true_type  is_allocator_aux(A const&);
std::false_type is_allocator_aux(...);

template<class A> struct is_allocator : decltype(is_allocator_aux(std::declval<A const&>())){};

which can used as this:

#include<memory>
#include<vector>
int main(){
    static_assert(is_allocator<std::allocator<double>>{});
    static_assert(not is_allocator<double>{}, "!");
    static_assert(not is_allocator<std::vector<double>>{}, "!");
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top