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.

2025-01-03


1001. Parameter type adjustment in dependent parameter types

Section: 9.3.4.6  [dcl.fct]     Status: review     Submitter: Jason Merrill     Date: 2009-11-08

According to 9.3.4.6 [dcl.fct] paragraph 5, top-level cv-qualifiers on parameter types are deleted when determining the function type. It is not clear how or whether this adjustment should be applied to parameters of function templates when the parameter has a dependent type, however. For example:

    template<class T> struct A {
       typedef T arr[3];
    };

    template<class T> void f(const typename A<T>::arr) { } // #1

    template void f<int>(const A<int>::arr);

    template <class T> struct B {
       void g(T);
    };

    template <class T> void B<T>::g(const T) { } // #2

If the const in #1 is dropped, f<int> has a parameter type of A* rather than the const A* specified in the explicit instantiation. If the const in #2 is not dropped, we fail to match the definition of B::g to its declaration.

Rationale (November, 2010):

The CWG agreed that this behavior is intrinsic to the different ways cv-qualification applies to array types and non-array types.

Notes, January, 2012:

Additional discussion of this issue arose regarding the following example:

    template<class T> struct A {
      typedef double Point[2];
      virtual double calculate(const Point point) const = 0;
    };

    template<class T> struct B : public A<T> {
      virtual double calculate(const typename A<T>::Point point) const {
        return point[0];
      }
    };

    int main() {
      B<int> b;
      return 0;
    }

The question is whether the member function in B<int> has the same type as that in A<int>: is the parameter-type-list instantiated directly (i.e., using the adjusted types) or regenerated from the individual parameter types?

(See also issue 1322.)

Additional notes (February, 2024)

There is implementation divergence for the following example (clang and EDG accept, gcc and MSVC reject):

  template<typename U>
  int f(const U t);

  int v;
  auto test = f(v);

  template<typename U>
  int f(U t) {
   static_assert(std::is_const<decltype(t)>::value, "t should be non-const");
   return 0;
  }

Possible resolution (option 1):

Change in 9.3.4.6 [dcl.fct] paragraph 4 as follows:

The type of a function is determined using the following rules. The type of each parameter (including function parameter packs) is determined from its own parameter-declaration (9.3 [dcl.decl]). After determining the type of each parameter, any parameter of type “array of T” or of function type T is adjusted to be “pointer to T”. After producing the list of parameter types, any top-level cv-qualifiers modifying a non-dependent parameter type are deleted when forming the function type. The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function's parameter-type-list. ...

Additional notes (December, 2024)

The resolution suggested above causes errors in ostensibly more common situations such as this one, requiring an Annex C entry:

  template <class T>
  void f(T*);                 // #1

  template <class T>
  void f(T* const) {}         // redeclaration of #2?

  int main() {
    f((int*)0);               // was ok, becomes ambiguous
  }

Possible resolution (option 2):

  1. Change in 9.3.4.6 [dcl.fct] paragraph 4 as follows:

    The type of a function is determined using the following rules. The type of each parameter (including function parameter packs) is determined from its own parameter-declaration (9.3 [dcl.decl]). After determining the type of each parameter, any parameter of type “array of T” or of function type T is adjusted to be “pointer to T”. If a function parameter type is adjusted from "array of T" and acquires the array type through a dependent type, and any top-level cv-qualifier modifies the dependent parameter type in any declaration of the templated function, the program is ill-formed; no diagnostic is required unless a cv-qualifier modifies the parameter type in a reachable definition of the function. After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type. The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function's parameter-type-list. ...

    [ Example:

    ...
      void h(int x(const int));   // #3
      void h(int (*)(int)) {}     // defines #3
    
      template<class T>
      int i(T);                   // #4
      template<class T>
      int i(const T) {}           // defines #4
      int v = i<int[]>(0);        // error: cv-qualified array parameter type in definition of template
    

    -- end example]

  2. Change in 13.2 [temp.param] paragraph 6 as follows:

    A non-type template-parameter shall have one of the following (possibly cv-qualified) types:
    • a structural type (see below),
    • a type that contains a placeholder type (9.2.9.7 [dcl.spec.auto]), or
    • a placeholder for a deduced class type (9.2.9.8 [dcl.type.class.deduct]).
    The top-level cv-qualifiers on the template-parameter are ignored when determining its type.
  3. Change in 13.2 [temp.param] paragraph 10 as follows:

    A non-type template-parameter of type “array of T” or of function type T is adjusted to be of type “pointer to T”. If the type of a non-type template-parameter is adjusted from "array of T" and acquires the array type through a dependent type, and any top-level cv-qualifier modifies the dependent type in any declaration of the template, the program is ill-formed; no diagnostic is required unless a cv-qualifier modifies the type of the template-parameter in a reachable definition of the template. [ Example:
      template<int* a>
      struct R { /* ... */ };
      template<int b[5]> struct S { /* ... */ };
      int p;
      R<&p> w;      // OK
      S<&p> x;      // OK due to parameter adjustment
      int v[5];
      R<v> y;       // OK due to implicit argument conversion
      S<v> z;       // OK due to both adjustment and conversion
    
      template<class T, const T x>
      struct U { /* ... */ };
      U<int[], nullptr> u;   // error: const array adjustment
    
    -- end example ]

    The top-level cv-qualifiers on the (possibly adjusted) type of a template parameter are ignored.