cplusplus / CWG

Core Working Group
23 stars 7 forks source link

CWG2896 [temp.deduct] Deducing template arguments from function declarations with noexcept-specifiers #537

Open sdkrystian opened 1 month ago

sdkrystian commented 1 month ago

Full name of submitter: Krystian Stasiowski

Reference (section label): [temp.deduct]

Link to reflector thread (if any): N/A

Issue description:

According to [temp.deduct.decl] p1:

In a declaration whose declarator-id refers to a specialization of a function template, template argument deduction is performed to identify the specialization to which the declaration refers. Specifically, this is done for explicit instantiations, explicit specializations, and certain friend declarations. This is also done to determine whether a deallocation function template specialization matches a placement operator new. In all these cases, P is the type of the function template being considered as a potential match and A is either the function type from the declaration or the type of the deallocation function that would match the placement operator new as described in [expr.new]. The deduction is done as described in [temp.deduct.type].

Consider the following:

template<bool B>
void f() noexcept(B);

template<>
void f() noexcept;

Despite the resolution of CWG2355, the explicit specialization of f is rejected by Clang, GCC, EDG, and MSVC because a template argument for B cannot be deduced from the noexcept-specifier. This approach is arguably correct, as it avoids instantiating the exception specification for all candidate templates (which could render the program ill-formed as the noexcept-specifier excluded from the deduction substitution loci). Moreover, deduction from the noexcept-specifier may not be immediately possible because it hasn't been parsed yet (e.g. for a class scope explicit specialization). The wording should be changed to reflect existing implementation practice.

Excluding the noexcept-specifier from deduction when deducing template arguments from a function declaration also necessitates its exclusion from deduction during partial ordering in such contexts, as template parameters only used in the noexcept-specifer would otherwise cause template argument deduction to always fail.

Suggested resolution:

Change [temp.deduct.decl] p1 as follows:

[...] In all these cases, P is the function type of the function template being considered as a potential match and A is either the function type from the declaration or the type of the deallocation function that would match the placement operator new as described in [expr.new]. Any noexcept-specifier is ignored when determining the types of P and A. The deduction is done as described in [temp.deduct.type].

Change [temp.deduct.partial] p3 as follows:

The types used to determine the ordering depend on the context in which the partial ordering is done:

  • In the context of a function call, the types used are those function parameter types for which the function call has arguments.
  • In the context of a call to a conversion function, the return types of the conversion function templates are used.
  • In other contexts the function template's function type outside of the noexcept-specifier is used.
sdkrystian commented 1 month ago

The noexcept-specifier should also be ignored when deducing template arguments taking the address of a function template, but I'm not sure whether it should be part of this issue.

jensmaurer commented 1 month ago

CWG2896

jensmaurer commented 1 month ago

It is not clear to me why the address-of-function-template case also needs to ignore the exception specification.

Also, for the two carve-outs here, do they apply only to a noexcept-specifier on the top level of a function declaration, or also to noexcept-specifiers on function parameters of (e.g.) function pointer type? "Any noexcept-specifier is ignored" sounds very much like the latter, but it seems the former is intended.

sdkrystian commented 1 month ago

Minor grammar nit:

[...] we also need to avoid consider the exception specification [...]

"consider" should be "considering".

sdkrystian commented 1 month ago

It is not clear to me why the address-of-function-template case also needs to ignore the exception specification.

The phrasing of [except.spec] p13.1 seems to suggest that the exception specification is instantiated only for the (unique) function selected by overload resolution. The address-of-function-template case requires deduction be performed for the candidate templates, which can result in errors outside the immediate context, e.g.:

struct A 
{ 
    static constexpr bool x = true;
};

template<typename T, typename U>
void f(T, U*) noexcept(T::x); // #1

template<typename T, typename U>
void f(T, U) noexcept(T::y); // #2

void(&g)(A, int*) noexcept = f;

Excluding the noexcept-specifier from deduction and partial ordering in this case results in only the exception specification of #1 being instantiated.

Also, for the two carve-outs here, do they apply only to a noexcept-specifier on the top level of a function declaration, or also to noexcept-specifiers on function parameters of (e.g.) function pointer type? "Any noexcept-specifier is ignored" sounds very much like the latter, but it seems the former is intended.

Yes, they only apply to a noexcept-specifier on the top level of a function declaration.

jensmaurer commented 1 month ago

Fixed "considering" and clarified top-level noexcept-specifier.

Amended for address-of-function-template case.