c++templatesinheritancec++17type-traits

Conditionally hiding methods in a template depending on base type


I have a template that is derived from either std::vector or std::list. I need to hide all methods that enable altering the size of the underlying container. I know that I can hide methods via the using declaration. The problem is that some relevant methods are only available for std::list. Is it possible to conditionally hide those methods without explicitly overloading them (which would be a hassle)? Is there some fancy std::enable_if syntax that I can apply to the using declaration as well?

template <class ValueType, class BaseType = std::vector<ValueType>,
  std::enable_if_t<std::is_same_v<BaseType, std::vector<ValueType>> || std::is_same_v<BaseType, std::list<ValueType>>,int> = 0
>
struct Test : public BaseType
{
  Test() = default;

private:
  // Hide base members that are not supposed to be used directly
  using BaseType::push_back;
  using BaseType::insert;
  using BaseType::assign;
  using BaseType::emplace_back;
  using BaseType::resize;
  using BaseType::swap;

  // These methods must be hidden BaseType==std::list, but are not available for std::vector
  //using BaseType::merge;
  //using BaseType::push_front;
  //using BaseType::splice;
  //using BaseType::emplace_front; 
}

As a workaround, I could write new base class wrappers that are selected via std::conditional, like this, but it is surely not too nice:

template<class T>
struct CVector : public std::vector<T>
{
    using std::vector<T>::vector;
protected:
    using std::vector<T>::assign;
    using std::vector<T>::emplace_back;
    using std::vector<T>::insert;
    using std::vector<T>::push_back;
    using std::vector<T>::resize;
    using std::vector<T>::swap;
};

template <class T>
struct CList : public std::list<T>
{
    using std::list<T>::list;
protected:
    using std::list<T>::assign;
    using std::list<T>::emplace_back;
    using std::list<T>::insert;
    using std::list<T>::push_back;
    using std::list<T>::resize;
    using std::list<T>::swap;
    using std::list<T>::emplace_front;
    using std::list<T>::merge;
    using std::list<T>::push_front;
    using std::list<T>::splice;
};

template <class ValueType, class BaseType = std::vector<ValueType>,
  std::enable_if_t<std::is_same_v<BaseType, std::vector<ValueType>> || std::is_same_v<BaseType, std::list<ValueType>>,int> = 0
>
struct Test : public std::conditional<std::is_same_v<BaseType, std::vector<ValueType>>, CVector<ValueType>, CList<ValueType>>::type
{
  Test() = default;
}

Solution

  • You could simplify it somewhat by making specializations of a base class template:

    template<class>
    struct Base; // not implemented for the types you don't support
    
    template <class T, class Allocator>
    struct Base<std::vector<T, Allocator>> : std::vector<T, Allocator> {
        using std::vector<T, Allocator>::vector;
    
       protected:
        using std::vector<T, Allocator>::assign;
        using std::vector<T, Allocator>::emplace_back;
        using std::vector<T, Allocator>::insert;
        using std::vector<T, Allocator>::push_back;
        using std::vector<T, Allocator>::resize;
        using std::vector<T, Allocator>::swap;
    };
    
    template <class T, class Allocator>
    struct Base<std::list<T, Allocator>> : std::list<T, Allocator> {
        using std::list<T, Allocator>::list;
    
       protected:
        using std::list<T, Allocator>::assign;
        using std::list<T, Allocator>::emplace_back;
        using std::list<T, Allocator>::insert;
        using std::list<T, Allocator>::push_back;
        using std::list<T, Allocator>::resize;
        using std::list<T, Allocator>::swap;
        using std::list<T, Allocator>::emplace_front;
        using std::list<T, Allocator>::merge;
        using std::list<T, Allocator>::push_front;
        using std::list<T, Allocator>::splice;
    };
    

    The usage would then be simpler:

    template<class T>
    struct Test : Base<T> {
        Test() = default;
    };
    
    int main() {
        Test<std::vector<int>> v;
        Test<std::list<int>> l;
    }
    

    or if you wish:

    template<class T, class BaseType = std::vector<T>>
    struct Test : Base<BaseType> {
        Test() = default;
    };
    
    int main() {
        Test<int> v;
        Test<int, std::list<int>> l;
    }
    

    or if you don't want to repeat the value type when specifying the container:

    template <class T, template <class, class> class BaseType = std::vector,
              class Allocator = std::allocator<T>>
    struct Test : Base<BaseType<T, Allocator>> {
        Test() = default;
    };
    
    int main() {
        Test<int> v;
        Test<int, std::list> l; // note: `int` not needed
    }