Suppose we have a current class:
template<typename T>
struct Inheriter : public T {};
Note that its instantiation would be well-formed only if T
is a class
/struct
that is not declared as final
.
Then, with the power of std::void_t
and SFINAE we may check in compile-time whether the type is well-formed or ill-formed. Let's use it to write our own naive implementation of std::is_final
:
template<typename T, typename = void>
struct IsFinal : public std::true_type{};
template<typename T>
struct IsFinal<T, std::void_t<decltype(Inheriter<T>{})>> : public std::false_type{};
For any arbitrary type T
compiler is expected to instantiate the most specialized version of IsFinal
, and if the substitution would fail, then try to instantiate the least specialized version. And only if all of them are failed, then compiler must spit out an error.
But on Clang 14 and GCC 11.2 (which are the latest at this moment), when we pass some final
type as a template parameter to IsFinal
, we get a compilation error (https://godbolt.org/z/q8jxP3Thc).
Why doesn't the compiler try to instantiate the least specialized version of IsFinal
, and spits out an error straight up? Is this a compiler bug or some tricky part of the C++ language standard?
T{}
is failing substitution, this is discovering that the typeInheriter<T>
is ill-formed. The same would happen if the definition ofT
fails astatic_assert
-- which isn't actually a valid substitution-failure -- or ifT
was an invalid type likevoid&
– Germannstd::void_t
is – Capricorn