Généralement, cela se justifie pas en C++, comme d'autres réponses ici ont noté. En C++, nous avons tendance à définir les types génériques basés sur d'autres contraintes autres que "hérite de cette classe". Si tu voulais vraiment le faire, c'est assez facile à faire en C++11 et <type_traits>
:
#include <type_traits>
template<typename T>
class observable_list {
static_assert(std::is_base_of<list, T>::value, "T must inherit from list");
// code here..
};
Cette brise beaucoup de concepts que les gens attendent en C++. Il est préférable d'utiliser des astuces comme la définition de vos propres traits. Par exemple, peut - observable_list
veut accepter n'importe quel type de conteneur qui a les typedefs const_iterator
et begin
et end
membre de la fonction qui retourne l' const_iterator
. Si vous restreindre à des classes qui héritent de list
ensuite un utilisateur qui dispose de son propre type qui n'héritent pas de list
mais fournit ces fonctions de membre et de typedefs serait pas en mesure d'utiliser votre observable_list
.
Il y a deux solutions à ce problème, l'un d'eux est de ne pas contrainte de rien et s'appuient sur duck-typing. Un gros con de cette solution est qu'elle implique une énorme quantité d'erreurs qui peut être difficile pour les utilisateurs de grok. Une autre solution consiste à définir les traits de contrainte le type fourni pour répondre aux exigences d'interface. Le gros con de cette solution est qu'elle implique supplémentaire de l'écriture qui peut être considéré comme ennuyeux. Cependant, le côté positif, c'est que vous serez en mesure d'écrire vos propres messages d'erreur à la static_assert
.
Pour être complet, la solution de l'exemple ci-dessus est donné:
#include <type_traits>
template<typename...>
struct void_ {
using type = void;
};
template<typename... Args>
using Void = typename void_<Args...>::type;
template<typename T, typename = void>
struct has_const_iterator : std::false_type {};
template<typename T>
struct has_const_iterator<T, Void<typename T::const_iterator>> : std::true_type {};
struct has_begin_end_impl {
template<typename T, typename Begin = decltype(std::declval<const T&>().begin()),
typename End = decltype(std::declval<const T&>().end())>
static std::true_type test(int);
template<typename...>
static std::false_type test(...);
};
template<typename T>
struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {};
template<typename T>
class observable_list {
static_assert(has_const_iterator<T>::value, "Must have a const_iterator typedef");
static_assert(has_begin_end<T>::value, "Must have begin and end member functions");
// code here...
};
Il y a beaucoup de concepts illustré dans l'exemple ci-dessus que la vitrine de C++11. Certains termes de recherche pour les curieux sont variadic templates, SFINAE, expression SFINAE, et le type de traits.