8

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?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Fedor
  • 17,146
  • 13
  • 40
  • 131
  • 2
    Only guessing here, but probably for the same reason that a name for the inner parameter can be specified but it is ignored. You can apply the concepts to the "second" `U` in your example and it works as expected. Also note that a template with no valid instatiations is ill-formed NDR. – super Aug 02 '21 at 14:06
  • 1
    My guess is that `template typename T` requires that, whenever `False` is satisfied, `T` is valid, which is [vacuously true](https://en.wikipedia.org/wiki/Vacuous_truth). In other words, `typename U` implies `False U`, whereas `False` implies `True` (with `True` being the concept counterpart of `typename`). When `T` is used as in `T`, the template that `T` refers to (namely `S`) is used directly, bypassing the `False` in the template template parameter. (See [contravariance](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science))) – L. F. Aug 02 '21 at 14:10
  • 1
    Can be simplified to [that](https://gcc.godbolt.org/z/cr77ejfP3) (removing `U`) IMO. – Jarod42 Aug 02 '21 at 14:38
  • 1
    See [this](https://eel.is/c++draft/temp.arg.template#example-4) example in the draft standard. – n. m. could be an AI Aug 02 '21 at 14:52
  • 1
    @Fedor Your example is ill-formed, NDR because `False` is never satisfied. The duplicate question is not about substitution failure, but about why a program is ill-formed, NDR. This is a poor excuse to close the question, as the real issue has nothing to do with the NDR. Rewrite the question in such a way that the constraint is only sometimes satisfied, so that it is not ill-formed any more. – n. m. could be an AI Aug 02 '21 at 15:22

1 Answers1

7

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.
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • 1
    "... then this is ill-formed" [It is not](https://gcc.godbolt.org/z/9TE461qcf). – Barry Aug 02 '21 at 18:10
  • Thanks. I made template-argument and template-parameter unrelated, and now GCC refuses the code: https://gcc.godbolt.org/z/d7nT7vhM6. But Clang and MSVC still accept it, so I assume there are some bugs in them. – Fedor Aug 02 '21 at 18:41
  • @Barry You are right, I forgot one rule: if the parameter is not constrained at all, constraints of the argument are ignored. I will fix the example soon. – n. m. could be an AI Aug 02 '21 at 19:19