12

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).

2 Answers 2

15

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{};

Here it is working live.

Sign up to request clarification or add additional context in comments.

10 Comments

Awesome! That's indeed pretty straight-forward, when you see it. Helped me a lot to better understand the requires statement. Only downside is that VS IDE keeps complaining about a failed type constraint even though it compiles fine.
@dba - I believe VS's intellisense is still a WIP as far as full C++20 support is concerned. If you want to appease it, you may try using a helper function template instead of the lambda (like namespace detail { template<typename X> void helper(A<X>&); } - I don't have VS, so I don't know it it will work for sure, but it may be easier for the engine to comprehend.
You mean to put that line inside the class definition of B? That doesn't help. And putting that line inside the requires clause is not accepted by the compiler.
@dba - That's a namespace with an implementation detail. It's meant to (and can only) be placed in another namespace, usually the one containing the concept.
Oh, I see, so I need to call detail::helper(t) inside requires. And indeed, that appeases intellisense.
|
3

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.

1 Comment

Does not compile with clang

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.