Concept to check for a nested class template

Viewed 250

Suppose I want to be able to write:

template<class T> concept WithNestedTemplate = ...;
struct F { template<class> using nested = int; };
static_assert(WithNestedTemplate<F>);
static_assert(!WithNestedTemplate<int>);

That is, WithNestedTemplate should check for the existence of T::template nested a member class template or alias template with signature template<class> class.

I can write a helper concept:

template<template<class> class> concept TemplateOfᐸclassᐳ = true;
template<class T> concept WithNestedTemplate = TemplateOfᐸclassᐳ<T::template nested>;

But this gives false positives in gcc (the concept erroneously accepts int, e.g.), it's difficult to give the helper concept a sensible name, and now its definition is potentially some way away from where it is used.

Or I can write a generic lambda with tparams:

template<class T> concept WithNestedTemplate = requires {
    []<template<class> class>(){}.template operator()<T::template nested>(); };

But this currently doesn't work in clang (it doesn't like lambdas in unevaluated context), is abominably ugly, and possibly unclear to the reader.

Is there a better way to do this - preferably one that works across all major compilers?

I should probably mention that instantiating T::template nested as the test is not going to work, since it would be legitimate for it to be constrained.

2 Answers

Make a templated struct taking a template <class> class and check that the struct can be instantiated:

template <template <class> class> struct TakesTemplate {};
template<class T> concept WithNestedTemplate = requires {
    typename TakesTemplate<T::template nested>;
};

Compiler Explorer link

But this gives false positives in gcc

I think this is because temp.constr.normal#1.4 IIUC:

The normal form of a concept-id C<A1, A2, ..., An> is the normal form of the constraint-expression of C, after substituting A1, A2, ..., An for C's respective template parameters in the parameter mappings in each atomic constraint. If any such substitution results in an invalid type or expression, the program is ill-formed; no diagnostic is required.


But this currently doesn't work in clang (it doesn't like lambdas in unevaluated context)

You can just return true from the lambda and invoke it to form a constant expression:

template<class T> concept WithNestedTemplate =
    []<template<class> class>(){
        return true;
    }
    .template operator()<T::template nested>();

This should be ok because it is an atomic constraint IIUC

To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression, the constraint is not satisfied.

Related