Search code examples
c++inheritancepolymorphismstatic-methodsvirtual-functions

Force static method in class based on interface in C++


I am making a vector-class which has the option to take an allocator as a template parameter. To ensure a user-defined allocator works with the vector, I provide an interface which sets the minimum requirements for a given allocator. Since the allocator is just used to allocate and deallocate, it has no member variables, and all methods are static. Is there a way to make sure any implementation has a given set of methods that are also static? I know you cannot have a method be static and virtual at the same time, but I essentially want any allocator derived from base_allocator to have a set of methods, and for those methods to be static. In short: Can you work around the virtual static contradiction in c++?

This is my current code:

// in namespace mem_core
// Interface for usage of custom allocators with the custom:: namespace containers.
template <typename T>
class base_allocator {
public:
    using T_ptr = T*;
    using T_ref = T&;

    virtual T_ptr address(const T_ref value);

    // memory methods should allocate for amount elements of type T
    virtual void deallocate(T_ptr const ptr, const size_t& count) = 0;
    virtual T_ptr allocate(const size_t& amount) = 0;
};

_MEMORY_CORE_END_ // namespace mem_core }

#undef _MEMORY_CORE_BEGIN_
#undef _MEMORY_CORE_END_

// default allocator CLASS TEMPLATE
template <typename T>
class allocator : public mem_core::base_allocator<T> {
public:

    using value_type = T;
    using T_ptr = T*;
    using T_ref = T&;

    static T_ptr address(T_ref value) noexcept {
        return mem_core::addressof<T>(value);
    }

    static void deallocate(T_ptr const ptr, const size_t& count) {
        mem_core::deallocate<T>(ptr, count);
    }

    static T_ptr allocate(const size_t& amount) {
        return mem_core::allocate<T>(amount);
    }
};

This of course works, but there is no guarantee that a user-defined allocator has made the virtual methods static, as that is a requirement for the allocator to work within the vector-class. e.g. like this:

template <typename T, typename alloc_type = allocator<T>>
class vector {
public:

    using iterator = vector_iterator<vector<T, alloc_type>>;

    using T_ptr = T*;
    using T_ref = T&;

    using value_type = T;
private:
    T_ptr m_data;
    size_t m_size;
    size_t m_capacity;
public:
    vector() : m_data(nullptr), m_size(0), m_capacity(0) noexcept {};

    vector(const size_t& initial_cap) : m_size(0) {
        m_data = alloc_type::allocate(m_capacity); // Requires static allocate method
        m_capacity = initial_cap;
    }
// rest of vector class ...

Solution

  • It's possible to require a class with certain methods defined as static using SFINAE. The following example uses C++20's concepts, but with a little bit of work can be adapted to use pure SFINAE, since that's the only thing that's required here. This example defines a concept has_static_methods that requires a class to implement both "function" and "function2" as static methods. Non-static methods, virtual or not, fail:

    #include <iostream>
    #include <type_traits>
    
    template<typename T> struct is_function : public std::false_type {};
    
    template<typename Ret, typename ...Args>
    struct is_function<Ret (*)(Args...)> : public std::true_type {};
    
    struct not_static {
    
        void function();
        static void function2();
    };
    
    struct is_static {
        static void function();
        static void function2();
    };
    
    template<typename T>
    concept has_static_methods = is_function<decltype(&T::function)>::value &&
        is_function<decltype(&T::function2)>::value;
    
    // Ok's template parameter type must implement both static methods.
    
    template<has_static_methods T> void ok()
    {
    }
    
    int main()
    {
        ok<is_static>();        // OK
        ok<not_static>();       // Error, unsatisfied contsraint.
        return 0;
    }
    

    And with a little bit of extra work it's possible to enforce static methods with specific signatures.

    Now, keep in mind that this doesn't really prevent anyone from defining a subclass with non-static methods. This requires any class that gets passed as a parameter to your template to meet this constraint.