Boost logo

Boost :

Subject: Re: [boost] [static_if] Is there interest in a `static if` emulation library?
From: Sebastian Redl (sebastian.redl_at_[hidden])
Date: 2014-09-08 11:45:36


On 08 Sep 2014, at 6:50, Lorenzo Caminiti <lorcaminiti_at_[hidden]> wrote:

> Hello all,
>
> I was thinking about the problem of disabling data member declarations
> a bit more. I have a couple of questions on C++14 template variables
> (these are not Boost related but given I was trying to solve a problem
> raised by this mail thread I hope you all do not mind if I ask them
> here instead of say comp.lang.c++).
>
> 1. Why is not possible to use enabled_if with template variables?
>
> For example, I'd expect this declaration to be ignored by the compiler
> when B is 0 and the use of n<0> to fail at (b). Instead clang 3.4.2
> errors at (a) saying I cannot use enable_if there when B is 0.
>
> #include <type_traits>
>
> template< bool B >
> typename std::enable_if<B, bool>::type n = B; // (a)
>
> int main ( ) {
> n<1>;
> n<0>; // (b)
> return 0;
> }

enable_if only works in conjunction with SFINAE.

Look at that line of code of yours. First, think about what this line is:

n<1>;

It’s not an explicit template instantiation, even though it may look like that. It’s an expression statement, containing a single declaration reference expression, referencing the variable template instantiation n<1>.

This instantiates the variable template n with true for B. This means it instantiates std::enable_if<true, bool> and looks inside for the nested type “type”, which it finds to be an alias for bool. It assigns an initial value of true to that variable.

The declref expression thus returns the value of that variable (initially true). Since nothing is done beyond that, the value is ignored. But let’s imagine it’s not just a reference, but, say, a function call:

void launch_missiles(bool allow_inflight_abort);

launch_missiles(n<1>);

Now the value is taken and passed to that function.

What about this?

n<0>;

It’s the same thing as before. Let’s make use of the variable and pass it to something (because not using a variable is unrealistic).

launch_missiles(n<0>);

Ok, so what does this code mean? Instantiation n with false tries to instantiate std::enable_if<false, bool> and then look for “type” inside. There’s no such thing, though. What type does n<0> have, then?

So why does enable_if work in its intended use case? The reason is SFINAE - another part of C++ that is used quite differently from what it was probably intended for. SFINAE goes back a long way, but it is kind of implicit. The 1990 C++ ARM (which was the starting point for the C++ standard) described a very different way of using function templates than what we have today, but it said:

“Overloading resolution for template functions and other functions of the same name is done in three steps:
[1] Look for an exact match on functions; if found, call it.
[2] Look for a function template from which a function that can be called with an exact match can be generated; if found, call it.”

Note that the ARM description of templates is still very vague - the feature is marked “experimental”. However, the formulation “can be generated” already hints that the compiler will try to generate the necessary function from templates, but not error out completely if it encounters an error.

I’m running out of time trying to trace the history of SFINAE, but I believe it was never intended to allow complicated overload resolution choices, but only to prevent accidental errors.

class frobber {
  typedef int difference;
};
class frobnicator {
  typedef int difference;
};
// Eh, one template is good enough for all of these:
template <typename Frob>
Frob operator -(const Frob& f, typename Frob::difference d) {
  return generic code;
}

void foo() {
  9 - 4; // 1
}

Imagine that at //1, the compiler tries to instantiate all operator- templates for overload resolution, deduces int for the Frob parameter, and then chokes on trying to interpret int::difference. Simply by introducing the overload, you broke the language completely. SFINAE is really just a logical conclusion - it says that in such a case, the error shouldn’t bubble up.

enable_if is just a clever way of abusing this little feature. Fundamentally, enable_if is a way of turning a compile-time boolean constant into a compile error suitable for forcing SFINAE-driven advanced overload resolution. Therefore, where there is no SFINAE, enable_if is not useful.

Note that partial class template specialisation also uses SFINAE in some situations.

I hope issue #2 has already been sufficiently answered by the others.

Sebastian


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk