Search code examples
c++c++14sfinae

SFINAE on Error in Dependent Type causes unexpected hard error


I have code that I can simplify down to something like this:

#include <type_traits>

template <typename T>
struct dependent
{
    using type = typename T::type;
};

template <typename T>
typename dependent<T>::type
foo(const T& x);

bool foo(bool x) { return x; }

int main()
{
    foo(true);
}

This fails to compile with g++ 9.3 with --std=c++17 with the error:

test.cpp: In instantiation of 'struct dependent<bool>':
test.cpp:11:1:   required by substitution of 'template<class T> typename dependent<T>::type foo(const T&) [with T = bool]'
test.cpp:17:13:   required from here
test.cpp:6:11: error: 'bool' is not a class, struct, or union type
    6 |     using type = typename T::type;
      |           ^~~~

This is not what I would expect. I would expect that attempting to substitute bool for T in template <typename T> typename dependent<T>::type foo(const T& x) would be a failure, which is not an error. It seems SFINAE is not working for me, but I do not know why.

From the examples in the unofficial reference on SFINAE:

Substitution proceeds in lexical order and stops when a failure is encountered.

template <typename A>
struct B { using type = typename A::type; };

template <
  class T,
  class   = typename T::type,      // SFINAE failure if T has no member type
  class U = typename B<T>::type    // hard error if T has no member type
                                   // (guaranteed to not occur as of C++14)
> void foo (int);

I am hitting the case on class U = typename B<T>::type, but the "guaranteed to not occur as of C++14" bit seems to indicate that this should not be happening as of C++14. What gives?


Solution

  • Issue is that dependent<T> has type, but that one might be ill formed causing hard failure.

    You might make dependent SFINAE friendly:

    template <typename T, typename Enabler = void>
    struct dependent
    {
    };
    
    template <typename T>
    struct dependent<T, std::void_t<typename T::type>>
    {
        using type = typename T::type;
    };
    

    Demo