Мета-программирование на С++: параметр шаблона, который * должен * наследовать абстрактный класс

У меня есть абстрактный класс для сопоставимых + хешируемых значений:

class Key
{
public:
  virtual bool operator ==(const Key&) const = 0;
  virtual bool operator !=(const Key&) const = 0;
  virtual u32 hashcode() const = 0;
};

и некоторый конкретный класс C, который наследует это.

class C : public Key
{
private:
  u32 a, b;
public:
  static const C& null; // a prototype for representing a "no value" C
  // Some reasonable implementation; it just a pair
  // ...
};

и я хотел бы реализовать шаблонный класс HashSet:

template<class T inherits Key, const T& proto> class HashSet
{
  //...
};

T - тип значений, хранящихся в этих наборах. proto должен быть экземпляром T, который используется как "нулевое" значение типа T для целей включения. Я достаточно опытен с С++, но не особенно с TMP и, хотя кажется, что-то, что должно быть смущающе просто для снятия, я не могу понять, как что-то вроде моего псевдокода "класс T наследует Key" выполняется на С++. Я хочу иметь возможность создавать хеш-набор экземпляров C как:

HashSet<C, C::null> myset;

Может кто-нибудь, пожалуйста, скажите мне, какой правильный и идиоматический способ справиться с этой ситуацией на С++? Спасибо!

Ответ 1

Вы можете использовать std::enable_if_t и std::is_base_of для этого:

template<class T, const T& proto, 
         std::enable_if_t<std::is_base_of<Key,T>::value>* = nullptr> 
class HashSet
{
  //...
};

Теперь HashSet экземпляры действительны только в том случае, если T наследуется от Key.

std::enable_if_t - это функция С++ 14. Вы можете использовать typename std::enable_if<...>::type, если вы застряли с С++ 11.

Live Demo


Другой вариант - использовать static_assert:

template<class T, const T& proto>
class HashSet
{
    static_assert(std::is_base_of<Key, T>::value, "T must inherit from Key");
};

Это, возможно, немного яснее и дает вам более дружественное сообщение об ошибке, но ограничение вашего типа больше не указано в объявлении класса.


С Концепции мы получим ясность, лучшие сообщения об ошибках и сохраним наши ограничения в объявлении:

template <class Base, class Derived>                                                                                                                                                                                                           
concept bool IsBaseOf = std::is_base_of<Base, Derived>::value;

template<class T, const T& proto>
requires IsBaseOf<Key,T>
class HashSet
{};

Ответ 2

Может кто-нибудь, пожалуйста, скажите мне, какой правильный и идиоматический способ справиться с этой ситуацией на С++ будет?

Это просто не будет обрабатывать. Если пользователь передает тип, полученный из Key, тогда создание экземпляра будет работать, даже если вы не добавите это как явное требование в аннотации кода. Если пользователь передает недопустимый аргумент шаблона, ожидается, что все будет разорвано.

В следующей версии С++, вероятно, будет поддержка четких включений таких аннотаций, но в текущей версии С++, хотя есть некоторые трюки, которые вы можете использовать, за исключением ограниченных обстоятельств, идиоматический способ - просто не беспокоиться об этом.