Reputation: 493
Is there a way to use C++ concepts to require that a class is derived from a templated class, whose template parameter is again a derived class from another templated class.
Example:
template <class T>
class A{};
template <class T>
class B{};
class X{};
class Y : public A<X> {};
class Z : public B<Y> {};
How can I check in B
, that T
is of the form std::is_base_of<A<X>,T>
for some X
without specifying what X
is?
I don't want to add X
to the template paramter list of B
, because I don't want to change code at every instance where B
is derived from (e.g. the last line with class Z
).
Upvotes: 12
Views: 3426
Reputation: 192
Inspired by answer by @StoryTeller - Unslander Monica, I wrote more general concept which is customisable for any expected template class.
However, you might first ask yourself, whether it makes actually sense to restrict the template type? It makes your design less flexible, so that you can't later e.g. inject mock types for unit testing. IMHO it's usually better to write a concept, which requires your type to adhere to some specific contract (contain some member functions, constants, aliases, etc.) rather than to actually be a concrete class.
Having said that, here's the generalised solution:
/**
* @brief Checks if class type Specialisation (the implicit concept
* argument) is indeed a specialisation of TemplateClass type
* (e.g. satisfied for TemplateClass=SomeLibrary and
* Specialisation=SomeLibrary<A, B>). Also accepts classes
* deriving from specialised TemplateClass.
*
* @tparam PartialSpecialisation optional partial specialisation
* of the TemplateClass to be required
*/
template<class Specialization, template<typename> class TemplateClass,
typename ...PartialSpecialisation>
concept Specializes = requires (Specialization s) {
[]<typename ...TemplateArgs>(
TemplateClass<PartialSpecialisation..., TemplateArgs...>&){}(s);
};
Then for your use case:
template <Specializes<A> T>
class B{};
or even require specific partial specialisation of the desired class:
template<typename ...Args>
struct SomeBase {};
struct A {};
struct B {};
template<Specializes<SomeBase, A> BaseT>
struct Container {};
Container<SomeBase<A, B>> {}; // fine, first template arg is A
Container<SomeBase<B, B>> {}; // error, first template arg isn't A
See working live example here.
Upvotes: 3
Reputation: 170045
If you want to check for specialisations of A
specifically, that isn't too difficult.
template <class C>
concept A_ = requires(C c) {
// IILE, that only binds to A<...> specialisations
// Including classes derived from them
[]<typename X>(A<X>&){}(c);
};
The lambda is basically just a shorthand for a function that is overloaded to accept A
specialisations. Classes derived from such specialisations also count towards it. We invoke the lambda with an argument of the type we are checking... and the constraint is either true or false depending on whether the call is valid (the argument is accepted).
Then, just plug it in:
template <A_ T>
class B{};
Upvotes: 15