Is SFINAE on unspecializable std templates ill-formed?

Viewed 107

Is the following legal?

template<typename T>
std::enable_if_t<false, T> foo() {}

Usually, we have [temp.res]

The program is ill-formed, no diagnostic required, if:

  1. no valid specialization can be generated for a template [...] and the template is not instantiated [...]

Clearly, no valid specialization can be generated for foo... unless you consider that std::enable_if could possibly be specialized such that type is present as void regardless of the first bool argument.

struct S {};
template<bool B>
struct std::enable_if<B, S> : std::type_identity<void> {};

Except... you can't specialize std::enable_if by [meta.rqmts]

Unless otherwise specified, the behavior of a program that adds specializations for any of the templates specified in [meta] is undefined.

To summarize, if you wrote a custom enable_if that mimics std::enable_if, the first snippet is legal. The question is whether the prohibition on specializing std::enable_if makes it illegal?

This post is inspired by this answer. The current form is adapted from an implementation of an independent library.

1 Answers

The question is whether the prohibition on specializing std::enable_if makes it illegal?

I would say that yes, it does. For starters, since such specialization would lead to undefined behavior, we can completely ignore the possibility of this ever happening. The contract is breached there, so discussing legality further is moot1.

Now, since we don't have to worry about specialization at all, we (as someone trying to determine the behavior of a program/implementation) can rely on to the described behavior in the standard. The description of std::enable_if specifies that it has no ::type member when its bool argument is false. So we are guaranteed std::enable_if<false, T>::type is always ill-formed.

This leads to inevitably meeting the criteria in [temp.res]/8. And now the program is ill-formed NDR.


1 - That is after all the very purpose of the [meta] clause you quote. It's to allow assuming things about the behavior of standard library components. Specifically, so that an implementation may optimize around said expected behavior.
Related