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

2024-11-11


2847. Constrained explicit specializations of function templates at class scope

Section: 13.9.4  [temp.expl.spec]     Status: review     Submitter: Krystian Stasiowski     Date: 2023-12-15

(From submission #482.)

Consider:

  template<typename T>
  concept C = sizeof(T) > sizeof(char);
  template<typename T>
  concept D = sizeof(T) > sizeof(int) ;

  template<typename T>
  struct A 
  {
    template<typename U>
    constexpr int f(U) requires C<U> { return 0; }

    template<>
    constexpr int f(int) requires D<T> { return 1; }
  };

  static_assert(A<int>().f(0) == 0);   // #1

There is substantial implementation variance: GCC does not allow explicit specializations of function templates at class scope (contrary to the rule change introduced by issue 727), clang rejects them if a trailing-requires-clause is present, and EDG accepts, but ignores the constraint, causing #1 to fail.

Proposed resolution (reviewed by CWG 2024-03-01) [SUPERSEDED]:

  1. Add a new paragraph before 13.9.4 [temp.expl.spec] paragraph 8 as follows:

    An explicit specialization of a function shall not have a trailing requires-clause (9.3.1 [dcl.decl.general]). [ Example:

      template<typename T>
      concept C = sizeof(T) <= sizeof(int);
    
      template<typename T>
      struct A {
        template<typename U>
        void f(U) requires C<U>;
    
        template<>
        void f(char);    // OK
    
        template<>
        void f(short) requires (sizeof(T) >= 1); // error: trailing requires-clause not allowed
      };
    
      template<>
      template<typename U>
      void A<int>::f(U) requires C<U> {} // OK, explicit specialization is a template
    

    -- end example ]

    The placement of explicit specialization declarations for function templates ...

  2. Add another example at the end of 13.9.4 [temp.expl.spec] paragraph 15 as follows:

    [ Example:

       template<typename T>
       struct D {
         template<typename U>
         static constexpr int f(U);          // #1
    
         template<typename U>
         static constexpr int f(U) requires (sizeof(T) == 1);    // #2
    
         template<>
         constexpr int f(int)                // #3
         { return 1; }
       };
    
       template<> template<typename U>
       constexpr int D<signed char>::f(U) requires (sizeof(signed char) == 1)  // #4 
       { return 0; }
    
       static_assert(D<char>::f(0) == 1);          // overload resolution selects #2; #3 is a specialization for #2
       static_assert(D<char[2]>::f(0) == 1);       // overload resolution selects #1; #3 is a specialization for #1
       static_assert(D<signed char>::f(0) == 1);   // overload resolution selects #2; #3 is a specialization for #2
       static_assert(D<signed char>::f(0.0) == 0); // overload resolution selects #2; #4 is a specialization for #2
    

    -- end example ]

Additional notes (April, 2024)

The phrasing "an explicit specialization of a function" does not make sense.

Possible resolution:

  1. Add a new paragraph before 13.9.4 [temp.expl.spec] paragraph 8 as follows:

    An explicit specialization that declares a function shall not have a trailing requires-clause (9.3.1 [dcl.decl.general]). [ Example:

      template<typename T>
      concept C = sizeof(T) <= sizeof(int);
    
      template<typename T>
      struct A {
        template<typename U>
        void f(U) requires C<U>;
    
        template<>
        void f(char);    // OK
    
        template<>
        void f(short) requires (sizeof(T) >= 1); // error: trailing requires-clause not allowed
      };
    
      template<>
      template<typename U>
      void A<int>::f(U) requires C<U> {} // OK, explicit specialization is a template
    
      template<>
      template<>
      void A<short>::f(int) requires C<int> {} // error: trailing requires-clause for a declaration of a non-templated function
    

    -- end example ]

    The placement of explicit specialization declarations for function templates ...

  2. Add another example at the end of 13.9.4 [temp.expl.spec] paragraph 15 as follows:

    [ Example:

       template<typename T>
       struct D {
         template<typename U>
         static constexpr int f(U);          // #1
    
         template<typename U>
         static constexpr int f(U) requires (sizeof(T) == 1);    // #2
    
         template<>
         constexpr int f(int)                // #3
         { return 1; }
       };
    
       template<> template<typename U>
       constexpr int D<signed char>::f(U) requires (sizeof(signed char) == 1)  // #4 
       { return 0; }
    
       static_assert(D<char>::f(0) == 1);          // overload resolution selects #2; #3 is a specialization for #2
       static_assert(D<char[2]>::f(0) == 1);       // overload resolution selects #1; #3 is a specialization for #1
       static_assert(D<signed char>::f(0) == 1);   // overload resolution selects #2; #3 is a specialization for #2
       static_assert(D<signed char>::f(0.0) == 0); // overload resolution selects #2; #4 is a specialization for #2
    

    -- end example ]