With regards to the following code (https://wandbox.org/permlink/nhx4pheijpTF1ohf reproduced below for convenience)
#include <type_traits>
#include <utility>
namespace foo_name {
template <typename T>
void foo();
template <>
void foo<int>();
template <typename T>
struct c_size;
template <>
struct c_size<int> : public std::integral_constant<int, 1> {};
} // namespace foo_name
template <typename Type>
class Foo {
public:
template <typename T>
static decltype(auto) impl(T&& t) {
using foo_name::foo;
return foo(std::forward<T>(t));
}
};
class Something {};
template <typename Type, typename T = std::decay_t<Type>>
using EnableIfHasFoo = std::void_t<
decltype(Foo<T>::impl(std::declval<T>())),
decltype(foo_name::c_size<Type>::value)>;
template <typename Type, typename = std::void_t<>>
class Test {};
template <typename Type>
class Test<Type, EnableIfHasFoo<Type>> {};
int main() {
static_cast<void>(Test<Something>{});
}
The code above exits with an error because the instantiation of Foo<T>::impl()
causes a hard error and is not usable in a SFINAE context. But the strange thing here is that when you switch the order of things in the void_t
in EnableIfHasFoo
(to the following https://wandbox.org/permlink/at1KkeCraNwHGmUI), it will compile
template <typename Type, typename T = std::decay_t<Type>>
using EnableIfHasFoo = std::void_t<
decltype(foo_name::c_size<Type>::value),
decltype(Foo<T>::impl(std::declval<T>()))>;
Now the questions are
- Why does the code initially not compile? The instantiation of
Foo<T>::impl()
is in context of the substitution, so it should work? - Substituting
foo_name::foo(T)
in place of the first argument tovoid_t
will make it compile (see https://wandbox.org/permlink/g3NaPFZxdUPBS7oj), why? How does adding one extra layer of indirection make the situation different? - Why does the order in
void_t
make a difference, does the compiler short circuit the expressions within the type pack?