2

I'm reading the C++ Programming Language, 4th Edition by Bjarne Stroustrup and in Chapter 28 about metaprogramming in Section 28.4.4 there is a code similar to the following example:

#include <type_traits>
#include <string>

struct substitution_failure {};

template <typename T>
struct substitution_succeeded : std::true_type {};

template <>
struct substitution_succeeded<substitution_failure> : std::false_type {};

double f(double x)
{
  return x;
}

template <typename T>
class get_f_result
{
  template <typename X>
  static auto check(const X& x) -> decltype(f(x));

  static substitution_failure check(...);

public:
  using type = decltype(check(std::declval<T>()));
};

template <typename T>
struct has_f : substitution_succeeded<typename get_f_result<T>::type> {};

template <typename T>
constexpr bool has_f_v()
{
  return has_f<T>::value;
}

template <typename T>
struct Foo
{
  template <typename = std::enable_if_t<has_f_v<T>()>>
  T foo(const T& t)
  {
    return f(t);
  }
};

int main()
{
  Foo<std::string> foo;
  return 0;
}

I expect thanks to the SFINAE the example to compile successfully after the foo method of the Foo class is never called, but it compiles only on MSVC and refuses to compile on GCC and Clang with the following error:

error: no type named 'type' in 'struct std::enable_if<false, std::__cxx11::basic_string<char> >'
 2611 |     using enable_if_t = typename enable_if<_Cond, _Tp>::type;

Why the example compiles only on MSVC and how to fix it to compile successfully also on GCC and Clang?

bobeff
  • 3,543
  • 3
  • 34
  • 62
  • Might be an MSVC error where it's supposed to figure out the default value for the type argument of `Foo::foo` immediately even if `foo` isn't instantiated but it doesn't do so. – Nathan Pierson Aug 09 '22 at 17:06
  • @Nathan MSVC compiles without error or warning even if you add another member to `Foo` and actually call that. (I also thought that it may be optimizing-out the whole `foo` instance, but that isn't it.) – Adrian Mole Aug 09 '22 at 17:19
  • 3
    There is no SFINAE in `Foo::foo` because `T` is already fixed by `Foo` itself. See [here](https://stackoverflow.com/questions/30953248/why-doesnt-sfinae-enable-if-work-for-member-functions-of-a-class-template). – Evg Aug 09 '22 at 17:19
  • 2
    @Evg Cool! Using `template ()>>` (and making the return type and arg for `foo` a `U`) works on clang. – Adrian Mole Aug 09 '22 at 17:22

0 Answers0