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
Foo<T>::impl()
is in context of the substitution, so it should work?foo_name::foo(T)
in place of the first argument to void_t
will make it compile (see https://wandbox.org/permlink/g3NaPFZxdUPBS7oj), why? How does adding one extra layer of indirection make the situation different?void_t
make a difference, does the compiler short circuit the expressions within the type pack?1) and 2) have the same answer; SFINAE does not work with return type deduction since the body of a function is not in immediate context:
10 - Return type deduction for a function template with a placeholder in its declared type occurs when the definition is instantiated even if the function body contains a
return
statement with a non-type-dependent operand. [ Note: Therefore, any use of a specialization of the function template will cause an implicit instantiation. Any errors that arise from this instantiation are not in the immediate context of the function type and can result in the program being ill-formed (17.8.2). — end note ]
3) is a more interesting question; the short-circuiting is intentional, and is guaranteed by [temp.deduct]:
7 - [...] The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered.
This short-circuiting works for gcc, clang and ICC, but unfortunately MSVC (as of CL 19 2017 RTW) gets it wrong, for example:
template<class T> auto f(T t) -> decltype(t.spork) { return t.spork; }
template<class T> auto g(T t) { return t.spork; }
int x(...);
template<class...> using V = void;
template<class T> auto x(T t) -> V<decltype(f(t)), decltype(g(t))> {}
int a = x(0);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With