This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 116a. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-12-19


2589. Context of access checks during constraint satisfaction checking

Section: 13.5.2.3  [temp.constr.atomic]     Status: review     Submitter: Jason Merrill     Date: 2019-10-02

Consider:

  template<class T> concept ctible = requires { T(); };

  class A {
    template <class T> friend struct B;
    A();
  };

  template <class T> struct B;
  template <ctible T> struct B<T> { T t; };
  B<A> b;  // #1

  template <class T> struct C { };
  template <ctible T> struct C<T> { T t; };
  C<A> c;  // #2

Should the context of instantiation be considered for satisfaction checking? If satisfaction checking were always performed in an unrelated context, neither partial specialization is used, and #1 would be ill-formed (because B is incomplete), but #2 would be well-formed. If the satisfaction checking were performed in the context of the constrained declaration, #1 would be well-formed and #2 would be ill-formed, no diagnostic required, because the validity of A() is different in that context. That rule, however, could also consider the context, in which case #2 would also be well-formed.

The decision affects the amount of caching that an implementation can perform.

Subclause 13.5.2.3 [temp.constr.atomic] paragraph 3 should be clarified one way or another:

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 (7.3.2 [conv.lval]) 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.

Proposed resolution [SUPERSEDED]:

Change in 13.5.2.3 [temp.constr.atomic] paragraph 3 as follows:

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; access checking is performed in the context in which the constraint-expression or requires-expression appears. Otherwise, the lvalue-to-rvalue conversion (7.3.2 [conv.lval]) is performed if necessary, and E shall be a constant expression of type bool. ...

Additional notes (December, 2024)

Reflector review yielded an improved resolution.

Proposed resolution [SUPERSEDED]:

Append a new paragraph after 11.8.1 [class.access.general] paragraph 9:

Access for a default template-argument (13.2 [temp.param]) is checked in the context in which it appears rather than at any points of use of it. [Example 5: ... —end example]

Access for an atomic constraint is checked in the context in which its expression appears, or for an immediately-declared constraint, in the context where its type-constraint appears, rather than at any point where its satisfaction is checked. [ Example:

  template<class T> concept ctible = requires { T(); };

  class A {
    template<class T> friend struct B;
    A();
  };

  template<class T> struct B;
  template<ctible T> struct B<T> { T t; };
  B<A> b;                    // error: uses incomplete primary template of B

  template<class T> struct C { };
  template<ctible T> struct C<T> { T t; };
  C<A> c;                    // OK, not using the partial specialization

  template<typename T, bool b> concept C = b;
  class U { static constexpr bool enabled = true; friend struct X; };
  struct X {
   template<typename U, C<U::enabled> V> struct A {};
  };
  struct Y {
   template<typename U, C<U::enabled> V> struct A {};
  };
  X::A<U, int> x;    // OK
  Y::A<U, int> y;    // error: cannot access U::enabled

--end example ]

Additional notes (December, 2024)

The resolution was amended.

Proposed resolution:

Append a new paragraph after 11.8.1 [class.access.general] paragraph 9:

Access for a default template-argument (13.2 [temp.param]) is checked in the context in which it appears rather than at any points of use of it. [Example 5: ... —end example]

Access for an atomic constraint is checked in a context unrelated to any class or function. [ Example:

  template<bool B> concept C = B;

  struct A {
    template<typename T> void f() requires C<T::value>;
  };
  struct B : A {
    using A::f;
    template<typename T> void f() requires C<T::value> && true;
  };

  class X { static constexpr bool value = true; friend struct A; };
  void g() {
    B().f<X>();    // error: no viable function
  }

--end example ]