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

2024-09-25


2412. SFINAE vs undeduced placeholder type

Section: 9.2.9.7  [dcl.spec.auto]     Status: review     Submitter: Mike Miller     Date: 2019-05-03

The status of the following example is not clear:

  template <typename T> auto foo(T);  // Not defined

  template <typename T> struct FooCallable {
    template<class U>
    static constexpr bool check_foo_callable(...) { return false; }

    template<class U, class = decltype(foo(U{})) >
    static constexpr bool check_foo_callable(int) { return true; }

    static constexpr bool value = check_foo_callable<T>(0);
  };
  static_assert(FooCallable<int>::value == false, "");

The static_assert causes the evaluation of the default template argument decltype(foo<int>(int{})). However, foo is not defined, leaving it with an undeduced placeholder return type. This situation could conceivably be handled in two different ways. According to 9.2.9.7 [dcl.spec.auto] paragraph 9,

If the name of an entity with an undeduced placeholder type appears in an expression, the program is ill-formed.

This would thus appear to be an invalid expression resulting from substitution in the immediate context of the declaration and thus a substitution failure.

The other alternative would be to treat the presence of an undeduced placeholder type for a function template as satisfying the requirements of 13.9.2 [temp.inst] paragraph 4,

Unless a function template specialization has been explicitly instantiated or explicitly specialized, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist or if the existence of the definition affects the semantics of the program.

and attempt to instantiate foo<int>. That instantiation fails because the definition is not provided, which would then be an error outside the immediate context of the declaration and thus a hard error instead of substitution failure.

CWG 2022-11-10

There is no implementation divergence on the handling of this example.

Possible resolution:

Change in 9.2.9.7.1 [dcl.spec.auto.general] paragraph 11 as follows:

If a variable or function with an undeduced placeholder type is named by an expression (6.3 [basic.def.odr]), the program is ill-formed. Once a non-discarded return statement has been seen in a function, however, the return type deduced from that statement can be used in the rest of the function, including in other return statements. [ Example: ...
  template <typename T> auto f(T);     // not defined

  template <typename T> struct F {
    template<class U>
    static constexpr bool g(...) { return false; }

    template<class U, class = decltype(f(U{})) >
    static constexpr bool g(int) { return true; }

    static constexpr bool value = g<T>(0);
  };
  static_assert(F<int>::value == false, "");
-- end example ]