Skip to content

CWG3179 [dcl.fct] p3 void parameter function and SFINAE #889

Closed
cplusplus/draft
#9147

Description

@mizvekov

Full name of submitter: Matheus Izvekov

Reference (section label): [dcl.fct]p3

Issue description:

[dcl.fct]p3 sentence 4 says:

A parameter list consisting of a single unnamed non-object parameter of non-dependent type void is equivalent to an empty parameter list.

Consider this example:

template<class T> void foo(std::void_t<T*>) {};
template void foo<int&>();

Here the function template foo declares a single unnamed non-object parameter of non-dependent type void.

Is this supposed to result in a substitution failure?

Suggested resolution:

Reject the function template declaration as invalid.

Change [dcl.fct] p3 sentence 4:

A parameter list consisting of a single unnamed non-object parameter of non-dependent type equivalent to ([temp.over.link]) void is equivalent to an empty parameter list.

Activity

  1. jensmaurer commented on Apr 21, 2026

    @jensmaurer
    Member

    template void foo<int&>(); would be ill-formed due to substitution failure (pointer-to-reference) per [temp.alias] p3.

    The current phrasing in [dcl.fct] p3 is the result of CWG2915 and CWG577. To me, the parameter of the "foo" template looks dependent, thus the explicit specialization doesn't find its primary template due to arity mismatch.

  2. mizvekov commented on Apr 21, 2026

    @mizvekov
    Author

    template void foo<int&>(); would be ill-formed due to substitution failure (pointer-to-reference) per [temp.alias] p3.

    The current phrasing in [dcl.fct] p3 is the result of CWG2915 and CWG577. To me, the parameter of the "foo" template looks dependent, thus the explicit specialization doesn't find its primary template due to arity mismatch.

    I think the example in [temp.alias]p3 is mistaken, the template-id is only instantiation-dependent, but it's not dependent.

    Looking at [temp.alias] p2, I don't see how substitution of void_t with dependent template arguments would result in a dependent type, since the underlying type is not dependent.

    So I think in the current model void_t specializations are always instantiation-dependent but not dependent.

    FWIW this touches a bit in possible resolutions for CWG1980.

    GCC Implements a resolution where there is a distinction between simple type aliases and non-simple ones.

    The rules involve the distinction of template type aliases with unused template parameters, involving some other criteria.

    But how GCC resolves this is by making these non-simple spcializations as dependent.

    Clang is soon implementing a different model, where these specializations stay only instantiation-dependent, but are nonetheless not equivalent under [temp.link].

    The difference can be observed in this example:

    struct impl5 {
    struct type {};
    };
    template<typename T> using alias5 = impl5;
    template<typename T> void k(typename alias5<T*>::type) {}
    template void k<int*>(impl5::type);

    Is the typename required above? Per GCC's resolution, yes.
    Per Clang's, the typename is still optional.

  3. jensmaurer commented on Apr 21, 2026

    @jensmaurer
    Member

    [temp.alias] p3 (and its example in particular) explicitly codifies design intent; see CWG1558.

    The term "instantiation-dependent" doesn't exist in the C++ standard, and it's probably past due that we introduce it. Until then, I think it's ok to talk about "non-dependent type void" in the rule in [dcl.fct]. (We probably want "not instantiation-dependent" there.)

    Other points: [temp.over.link] talks about expressions etc. being equivalent, but is almost silent about types being equivalent. Further, [temp.dep.type] p10.11 says a type is dependent if it is "denoted by a simple-template-id in which [...] any of the template arguments is dependent". Which is exactly the case for the void_t situation. Just because [temp.alias] p2 does some early substitution doesn't imply (to me) that [temp.dep.type] p10.11 (for the original template-id) no longer applies. And thus, the typename is required.

  4. mizvekov commented on Apr 21, 2026

    @mizvekov
    Author

    FWIW all the problems that exist for void_t, also have equivalent problems with certain usages of decltype, both in the same spirit, and also ones that involve value-dependent expressions, after CWG2064 was adopted.

    So if you replace the initial example with:

    template<void (*f)()> void foo(decltype(f())) {};
    void g();
    template void foo<&g>();
    

    According to https://eel.is/c++draft/temp.type#4, that decltype is not dependent.

  5. mizvekov commented on Apr 21, 2026

    @mizvekov
    Author

    Err, this is a better example:

    template<int &a> void foo(std::void_t<decltype((a))*>) {};
    int b;
    template void foo<b>();

    Another example:

    template<class T> void foo(std::void_t<decltype(int((T*){}))>) {}
    template void foo<int&>();

    Or, without void_t:

    template<class T> void foo(decltype((void)(T*)0)) {}
    template void foo<int&>();
  6. mizvekov commented on Apr 21, 2026

    @mizvekov
    Author

    Also, https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2258.pdf never touched the wording for [temp.dep.type].

    I think you could you make the case that this [temp.dep.type] p10.11 wording pre-exists that paper, before the distinction had to be made between class templates and type alias templates, as for example it exists in an earlier draft, N2009.

  7. jensmaurer commented on Apr 22, 2026

    @jensmaurer
    Member

    If you're saying that we miss a formal treatment of "instantiation-dependent" in the standard, I agree. I'm not finding a core issue for that, though. I thought we had one.

    Other related core issues for alias templates and "typename" in particular: CWG2236, CWG1390

  8. mizvekov commented on Apr 22, 2026

    @mizvekov
    Author

    I still think we can solve this issue (OP) at hand independently of the other concerns.

    So stepping back a little, if we go back to my original post, and instead replace my example with:

    template<class T> void foo(decltype((void)(T*)0)) {}
    template void foo<int&>();

    I believe that addresses your dependent type objections.

    With regards to my proposed wording, I still think it's correct, but still leaves things in an unfinished state because we have never established how equivalence rules work for these decltypes.

    For that, I agree we need to establish what instantiation-dependence is, and then say something to the effect that:
    "Two instantiation-dependent decltype-specifiers are equivalent if their expressions are equivalent".

    But IMO we don't need to wait for that to solve the issue, we know we need equivalency here, and we can provide a definition to that later.

  9. jensmaurer commented on Apr 22, 2026

    @jensmaurer
    Member

    Is there any reason we need to instantiate with int& ? Instantiating with plain int would also "work" in the sense that we want this to be ill-formed, because we don't match the arity of the primary template. Instantiating with int& raises "pointer-to-reference" questions that seem to sidetrack, right?

    [temp.type] p4 already says almost what you want to say for decltype.

    However, I won't introduce a use of the undefined term "instantiation-dependent" with the hopes of providing a definition later. The narrow concern here is a declaration f(U) where U resolves to "void". Can we just say "U is not dependent and no expression appearing in the parameter-declaration is type-dependent or value-dependent"?

    Hm... What about

    template<class T>
    struct S {
      using U = decltype((void)(T*)0);
      void f(U) {}  // zero parameters or any instantiation is ill-formed because forming f(void) ?
    };
    

    I notice that this rule is primarily for C compatibility, so maybe we can restrict that rule to non-templated functions, which avoids all "dependent" questions.

  10. changed the title [-][dcl.fct] p3 `void` parameter function and SFINAE[/-] [+]CWG3179 [dcl.fct] p3 `void` parameter function and SFINAE[/+] on Apr 22, 2026
  11. jensmaurer commented on Apr 22, 2026

    @jensmaurer
    Member
  12. mizvekov commented on Apr 22, 2026

    @mizvekov
    Author

    Is there any reason we need to instantiate with int& ? Instantiating with plain int would also "work" in the sense that we want this to be ill-formed, because we don't match the arity of the primary template. Instantiating with int& raises "pointer-to-reference" questions that seem to sidetrack, right?

    It works in the sense that it's an example of something we would disallow with my proposed resolution.

    The example with pointer-to-reference is just to illustrate a possible substitution failure, and why this matters.

    [temp.type] p4 already says almost what you want to say for decltype.

    Yeah, but it's talking about same type, which is not the same (ha!) as equivalent type.
    I think we will need this distinction.

    So decltype((void)(T*)0) is the same type as void, but is not equivalent to void.

    However, I won't introduce a use of the undefined term "instantiation-dependent" with the hopes of providing a definition later. The narrow concern here is a declaration f(U) where U resolves to "void". Can we just say "U is not dependent and no expression appearing in the parameter-declaration is type-dependent or value-dependent"?

    Hm... What about

    template<class T>
    struct S {
      using U = decltype((void)(T*)0);
      void f(U) {}  // zero parameters or any instantiation is ill-formed because forming f(void) ?
    };
    

    The latter, this doesn't matter because any instantiation is ill-formed.

    I think any definition of instantiation-dependence needs to propagate syntactically instead of semantically, ie a type becomes transitively instantiation-dependent if any instantiation-dependent types or expressions appear syntactically within it.

    So even if the underlying type of the alias U is instantiation-dependent, the type U itself is not instantiation-dependent.

    I notice that this rule is primarily for C compatibility, so maybe we can restrict that rule to non-templated functions, which avoids all "dependent" questions.

    That also works.

  13. mizvekov commented on Apr 29, 2026

    @mizvekov
    Author

    According to https://eel.is/c++draft/expr.prim.req#general-4, this is valid:

    constexpr bool b = requires(void) { true; };

    The current proposed resolution would disallow that, and this is fine for me.

    Otherwise, all the same problems would apply there.

  14. mizvekov commented on Apr 29, 2026

    @mizvekov
    Author

    Also, what about function types in general?

    template<class T> using F = T (*)(void);

    I think this proposed resolution would disallow void parameter type on all function types, wouldn't it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions