Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confusion with hard error in SFINAE

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

  1. Why does the code initially not compile? The instantiation of Foo<T>::impl() is in context of the substitution, so it should work?
  2. Substituting 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?
  3. Why does the order in void_t make a difference, does the compiler short circuit the expressions within the type pack?
like image 322
Curious Avatar asked Aug 09 '17 07:08

Curious


1 Answers

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);
like image 89
ecatmur Avatar answered Oct 31 '22 16:10

ecatmur