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

2024-03-20


2233. Function parameter packs following default arguments

Section: 9.3.4.7  [dcl.fct.default]     Status: CD5     Submitter: Richard Smith     Date: 2016-02-25

[Accepted as a DR at the June, 2018 (Rapperswil) meeting.]

The resolution of issue 777 attempts to make this valid:

   template<typename ...T> void f(int n = 0, T ...t);

However, it fails to do so, since any parameters resulting from the expansion of the pack would be ordinary parameters without default arguments following a parameter with a default argument, which is ill-formed. Thus only an empty pack would be usable with such a declaration, which violates the restriction against such contexts in 13.8 [temp.res] bullet 8.3.

Proposed resolution, February, 2018:

  1. Change 7.6.1.3 [expr.call] paragraph 4 as follows:

  2. When a function is called, each parameter (9.3.4.6 [dcl.fct]) shall be is initialized (9.4 [dcl.init], 11.4.5.3 [class.copy.ctor], 11.4.5 [class.ctor]) with its corresponding argument. If there is no corresponding argument, the default argument for the parameter is used; the program is ill-formed if one is not present. [Example:

      template<typename ...T> int f(int n = 0, T ...t);
      int x = f<int>();   // error: no argument for second function parameter
    

    end example] If the function is a non-static member function, the this parameter of the function (_N4868_.11.4.3.2 [class.this]) shall be is initialized with a pointer to the object of the call, converted as if by an explicit type conversion (7.6.3 [expr.cast]). [Note: There is no access or ambiguity checking...

  3. Change 9.3.4.7 [dcl.fct.default] paragraph 1 as follows:

  4. If an initializer-clause is specified in a parameter-declaration this initializer-clause is used as a default argument. [Note: Default arguments will be used in calls where trailing arguments are missing (7.6.1.3 [expr.call]). end note]
  5. Change 9.3.4.7 [dcl.fct.default] paragraph 4 as follows:

  6. For non-template functions, default arguments can be added in later declarations of a function in the same scope. Declarations in different scopes have completely distinct sets of default arguments. That is, declarations in inner scopes do not acquire default arguments from declarations in outer scopes, and vice versa. In a given function declaration, each parameter subsequent to a parameter with a default argument shall have a default argument supplied in this or a previous declaration, unless the parameter was expanded from a parameter pack, or shall be a function parameter pack. A default argument shall not be redefined by a later declaration (not even to the same value). [Example:

      void g(int = 0, ...);   // OK, ellipsis is not a parameter so it can follow
                              // a parameter with a default argument
      void f(int, int);
      void f(int, int = 7);
      void h() {
        f(3);                 // OK, calls f(3, 7)
        void f(int = 1, int); // error: does not use default from surrounding scope
      }
      void m() {
        void f(int, int);     // has no defaults
        f(4);                 // error: wrong number of arguments
        void f(int, int = 5); // OK
        f(4); // OK, calls f(4, 5);
        void f(int, int = 5); // error: cannot redefine, even to same value
      }
      void n() {
        f(6);                 // OK, calls f(6, 7)
      }
      template<class ... T> struct C {
        void f(int n = 0, T...);
      };
      C<int> c;               // OK; instantiates declaration void C::f(int n = 0, int)