12

Consider this:

template <typename T>
struct hash
{
     static_assert(false,"Not implemented.");
};

struct unhashable {};

template <typename T>
auto test(const T &t) -> decltype((*(hash<T> const *)nullptr)(t),int);

void test(...);

int main()
{
    std::cout << std::is_same<decltype(test(std::declval<unhashable>())),void>::value;
}

Apart from obviously missing headers, should this compile?

In other words, I am asking if the static assertion failure triggering inside a trailing decltype while deducing the return value of an overloaded function template is requested to halt the compilation, or if the overload has simply to be discarded.

On gcc 4.7, compilation fails. I am pretty positive though that this will compile ok in gcc 4.8 (but cannot check at this very moment). Who is right?

Morwenn
  • 21,684
  • 12
  • 93
  • 152
bluescarni
  • 3,937
  • 1
  • 22
  • 33
  • possible duplicate of [How can I force a client to call an explicitly specialized template instead of the primary template?](http://stackoverflow.com/questions/16286303/how-can-i-force-a-client-to-call-an-explicitly-specialized-template-instead-of-t) – Kerrek SB Apr 30 '13 at 15:00

2 Answers2

22

The compilation has to fail in any compliant compiler.

SFINAE rules are based on declarations and not definitions. (Sorry if I'm using the wrong terminology here.) What I mean is the following:

For a class/struct:

template < /* substitution failures here are not errors */ >
struct my_struct {
    // Substitution failures here are errors.
};

For a function:

template </* substitution failures here are not errors */>
/* substitution failures here are not errors */
my_function( /* substitution failures here are not errors */) {
    /* substitution failures here are errors */
}

In addition, the non existence of the struct/function for the given set of template arguments is also subject to SFINAE rules.

Now a static_assert can only appear in the regions where substitution failures are errors and, therefore, if it fires, you'll get a compiler error.

For instance the following would be a wrong implementation of enable_if:

// Primary template (OK)
template <bool, typename T>
struct enable_if;

// Specialization for true (also OK)
template <typename T>
struct enable_if<true, T> {
    using type = T;
};

// Specialization for false (Wrong!)
template <typename T>
struct enable_if<false, T> {
    static_assert(std::is_same<T, T*>::value, "No SFINAE here");
    // The condition is always false.
    // Notice also that the condition depends on T but it doesn't make any difference.
};

Then try this

template <typename T>
typename enable_if<std::is_integral<T>::value, int>::type
test(const T &t);

void test(...);

int main()
{
    std::cout << std::is_same<decltype(test(0)), int>::value << std::endl; // OK
    std::cout << std::is_same<decltype(test(0.0)), void>::value << std::endl; // Error: No SFINAE Here
}

If you remove the specialization of enable_if for false then the code compiles and outputs

1
1
Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
  • That's really clear. I guess this restriction of sfinae may be based on performance consideration. otherwise compiler would be so slow when resolving overloads https://en.cppreference.com/w/cpp/language/sfinae – TingQian LI Jul 02 '21 at 02:19
  • We can also conclude, do not rely on any error in function body after substitution to help resolving overloads. You have to explicitly put std::enable_if into where the "substitution" occurs. the SFINAE is called "Substitution failure" for a reason, substitution only occurs in "Declaration" part by definition at https://en.cppreference.com/w/cpp/language/sfinae. – TingQian LI Jul 02 '21 at 02:32
6

On gcc 4.7, compilation fails. I am pretty positive though that this will compile ok in gcc 4.8 (but cannot check at this very moment). Who is right?

The condition in your static assertion does not depend on any template parameter. Therefore, the compiler can immediately evaluate it to false when parsing the template, and realize that the assertion should fire - no matter whether you actually instantiate the template anywhere else.

The same should be true on any compiler.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Fair enough, I am gonna check exactly what happens on GCC 4.8 in a while. I guess the underlying question is then if static_assert() counts as a "substitution failure" or as something more serious? – bluescarni Apr 30 '13 at 16:06
  • @bluescarni: It is not a substitution failure, but rather a hard error, because it does not happen in the ["immediate context"](http://stackoverflow.com/questions/15260685/what-is-exactly-the-immediate-context-mentioned-in-the-c11-standard-for-whic) of the function type and its template parameter types – Andy Prowl Apr 30 '13 at 16:17
  • @Andy. I belive that the fact that the condition in the `static_assert` does not depend on `T` doesn't make any difference. Please, take a look at my answer and feel free to improve it. – Cassio Neri Apr 30 '13 at 16:50
  • @Cassio: Not sure if I am misunderstanding, however when the condition depends on a template parameter (e.g. `static_assert(std::is_arithmetic::value, "!"`), the compiler will evaluate it only when the template gets actually instantiated - and won't stop compilation as long as `T` is an arithmetic type. However, a condition that does not depend on `T` will be evaluated by the compiler even if the template is never instantiated, and if it evaluates to `false` (as in this case), compilation will stop in any case. This said, I agree with you that `static_assert()` should not be used for SFINAE – Andy Prowl Apr 30 '13 at 17:11
  • @Andy. Exactly, that's what I meant: `static_assert` cannot be used for SFINAE. I agree with you that the time of the evaluation depends on whether it's type dependent or not. – Cassio Neri Apr 30 '13 at 17:18