Comte_Zero
Comte_Zero

Reputation: 274

Excluding type from class template

I'm currently having trouble with stack messing up one of my templated classes.
The problem being that methods like pusk_back, begin or end aren't usable with that type.
So my goal here is to prevent contain from being instantiated with stack<T> or to add a condition preventing a stack instance to get to those parts of the code.
Here is what the class looks like:

template<typename T, template <typename...> typename U>
class contain {
    public:
        contain() : _container()
        {
        }
        ~contain()
        {
        }
        void push(T const&data)
        {
            this->_container.push_back(data);
        }
        void aff()
        {
            std::for_each(_container.begin(), _container.end(),
            [](T &var) {::aff(var);});
        }
        void add()
        {
            std::for_each(_container.begin(), _container.end(),
            [](T &var) {::add(var);});
        }
    private:
        U<T> _container;
};

I tried to specialize the template but wasn't able to find the syntax nor was I able to check the type of the variable at runtime.

Upvotes: 1

Views: 478

Answers (1)

Scheff&#39;s Cat
Scheff&#39;s Cat

Reputation: 20141

I must admit that templates still appear a little bit like black magic to me. I've successfully used template specializations in various situations (if I had no better idea) but never with template arguments. So, I tried...

When I tried on coliru (compiler g++ (GCC) 8.1.0) I started with -std=c++11 and soon got some terrible errors. For my luck, there was also a hint to switch to -std=c++17. I did and things become much better immediately. I remembered roughly that the template template arguments are not well supported in C++11. A short glance on cppreference proved me right:

Template template parameter

template < parameter-list > typename (C++17) | class name(optional) (1)
template < parameter-list > typename (C++17) | class name(optional) = default (2)
template < parameter-list > typename (C++17) | class ... name(optional) (3) (since C++11)

1) A template template parameter with an optional name.
2) A template template parameter with an optional name and a default.
3) A template template parameter pack with an optional name.

Having this clarified, I took part of OPs sample code and added a partial specialization for the template where I

  • left the first parameter
  • specialized the second parameter with std::stack.

Actually, this is not much different to how templates with type or value arguments are specialized.

cppreference has an article for this partial template specialization but using the term as search key word there should be many hits for books and tutorials. (I remember the book I once learned C++ templates from mentioned this also – of course.)

So, here my small sample to demonstrate this:

#include <iostream>
#include <stack>
#include <vector>

template<typename T, template <typename...> typename U>
class ContainerT {
  private:
    U<T> _container;
  public:
    ContainerT(): _container() { }
    ~ContainerT() = default;
    ContainerT(const ContainerT&) = default;
    ContainerT& operator=(const ContainerT&) = default;

    bool empty() const { return _container.empty(); }
    void push(const T &data) { _container.push_back(data); }
    T pop()
    {
      const T data = _container.back();
      _container.pop_back();
      return data;
    }
};

template<typename T>
class ContainerT<T, std::stack> {
  private:
    std::stack<T> _container;
  public:
    ContainerT(): _container() { }
    ~ContainerT() = default;
    ContainerT(const ContainerT&) = default;
    ContainerT& operator=(const ContainerT&) = default;

    bool empty() const { return _container.empty(); }
    void push(const T &data) { _container.push(data); }
    T pop()
    {
      const T data = _container.top();
      _container.pop();
      return data;
    }
};

#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__ 

int main()
{
  // for std::vector
  DEBUG(ContainerT<int, std::vector> vec);
  DEBUG(vec.push(1); vec.push(2); vec.push(3));
  DEBUG(while (!vec.empty()) std::cout << vec.pop() << '\n');
  // for std::stack
  DEBUG(ContainerT<int, std::stack> stk);
  DEBUG(stk.push(1); stk.push(2); stk.push(3));
  DEBUG(while (!stk.empty()) std::cout << stk.pop() << '\n');
  // done
  return 0;
}

It's obvious that ContainerT is instanced for ContainerT<int, std::vector>. Otherwise, the push() method couldn't compile.

For the ContainerT<int, std::stack>, the specialization is used instead. (Again, otherwise, the push() method couldn't compile.)

Output:

ContainerT<int, std::vector> vec;
vec.push(1); vec.push(2); vec.push(3);
while (!vec.empty()) std::cout << vec.pop() << '\n';
3
2
1
ContainerT<int, std::stack> stk;
stk.push(1); stk.push(2); stk.push(3);
while (!stk.empty()) std::cout << stk.pop() << '\n';
3
2
1

Live Demo on coliru

Upvotes: 1

Related Questions