c++c++20c++-conceptscontravariancetemplate-templates

Why is the concept in template template argument not verified?


C++20 allows the program to specify concept for template template argument. For example,

#include <concepts>

template <typename T> concept Char = std::same_as<T, char>;
template <typename> struct S {};
template <template <Char U> typename T, typename U> T<U> foo() { return {}; }

int main() { foo<S, int>(); }

the first template argument of the function foo is expected to be a single argument template.

The concept Char is defined to be true only the type char, so an attempt to satisfy it for int shall fail. Still above program is accepted by all compilers: https://gcc.godbolt.org/z/PaeETh6GP

Could you please explain, why the concept in template template argument can be specified, but it will be still ignored?


Solution

  • A template (actual) argument matches a template (formal) parameter if the latter is at least as specialised as the former.

    template <Char> typename T is more specialised than template <typename> struct S. Roughly speaking, template <Char> accepts a subset of what template <typename> accepts (the exact definition of what "at least as specialised" actually means is rather involved, but this is a zeroth approximation).

    This means that the actual argument can be used in all contexts where the formal parameter can be used. That is, for any type K for which T<K> is valid, S<K> is also valid (because S<K> is valid for any K).

    So it is OK to substitute S for T.

    If you do it the other way around:

    template<typename T> concept Any = true; 
    template<typename T> concept Char = Any<T> && std::same_as<T, char>;
    
    template<template<Any> class T> void foo();
              
    template<Char> struct S { };
              
    int main()  
    {           
        foo<S>(); 
    }
    

    then this is ill-formed, because (roughly) you can say T<int> but not S<int>. So S is not a valid substitute for T.

    Notes:

    1. Why would we need this always-true concept Any? What's wrong with simply saying template <template <typename> typename>? Well, that's because of a special rule: if the parameter is not constrained at all, constraints of the argument are ignored, everything goes.
    2. Why write Any<T> && std::same_as<T, char>;? To illustrate a point. The actual rules do not evaluate the boolean values of constraints, but compare constraints as formulae where atomic constraints serve as variables, see here. So the formal reason is that S has a conjunction of a strictly larger (inclusion-wise) set of atomic constraints than T. If S had the same or a strictly smaller set, it would be well-formed. If two sets are not ordered by inclusion, then neither template is more specialised, and there is no match.