'Why is substitution failure in parameter mappings considered ill-formed?

In this code,

template<class T, class U>
concept always_true = true;

template<class T>
concept always_true_if_tagged = always_true<T, typename T::tag>;

struct A {
    using tag = int;
};

static_assert(always_true_if_tagged<A>);
static_assert(!always_true_if_tagged<int>);  //GCC says this failed

GCC says that the second assertion failed. Both Clang and MSVC are agree to compile it.

I originally thought that it is ill-form with no diagnostic required, because of temp.constr.normal#1.4

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.

The substitution T::typename tag is a parameter mapping for always_true, so it is ill-formed; no diagnostic is required.

So my first two questions are

  1. Was I correct? (Is it ill-formed and did I cite the correct reason?)
  2. Why it should be ill-formed? (If I was correct.)

One of the solutions is to check the nested typename before. So the parameter mapping for always_true doesn't happen.

template<class T>
concept always_true_if_tagged =
    requires {typename T::tag;}
    && always_true<T, typename T::tag>;

Furthermore, temp.constr.atomic#3 says

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. Otherwise, the lvalue-to-rvalue conversion is performed if necessary, and E shall be a constant expression of type bool. The constraint is satisfied if and only if evaluation of E results in true. If, at different points in the program, the satisfaction result is different for identical atomic constraints and template arguments, the program is ill-formed, no diagnostic required.

If I write

template<class T>
concept always_true_if_tagged = bool(always_true<T, typename T::tag>);

bool(always_true<T, typename T::tag>) is an atomic constraint IIUC. The substitution of T::typename tag with T=int results in invalid type, so it should be well-formed and not satisfied.

So my last two(or four) questions are

  1. Why this doesn't apply to the first code or why [temp.constr.normal#1.4] doesn't apply here?
  • 3.1. Is this substitution a parameter mapping for concept-id always_true?

  • 3.2. Is the usage of always_true in concept C = always_true<T, typename T::tag> an atomic contraint? temp.constr.constr#general-1 says there are three different kinds of constraints: conjunctions, disjunctions and atomic constraints.

  1. Why can't concept C = always_true<T, typename T::tag> with T=int be well-formed like this one? (Likely the same as the second question though)


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source