In C++, consider the following example:
template <typename first, typename... params> struct q;
template <typename first> struct q <first>
{
q()
{
cout<<"x"<<endl;
}
};
template <typename first, typename... params> struct q
{
q()
{
cout<<"x";
q <params...> ();
}
};
int main()
{
q <int, int> ();
q <int, int, enable_if<true, bool>::type> ();
// q <int, int, enable_if<false, bool>::type> ();
}
I defined a template struct that accepts an arbitrary amount of parameters. I then instantiate it with a set of parameters. Each constructor will build a new q, with all its parameters but the first. A q with only one parameter won't call any more recursion.
Every time the constructor of a q is called, it will print out an "x". Therefore, the first line of the main will cause the program to print out "xx", while the second will print out an "xxx" since the enable_if is actually enabled. Now, I would have greatly appreciated if the third line would cause my program to print out "xx" (that is: build a q ). Sadly, what I get is the error
No template named 'type' in std::enable_if <false, bool>
What could I do to correct my example and make it work?
Thank you
You have misunderstood the use of std::enable_if
. (Many of us did, to begin with).
This has led you to ask a technically misguided question instead of the more
general one you want answered. The question you want answered is How can my template use or
ignore a template argument, based only a compiletime condition?, and it's not
a bad one.
You have the impression that std::enable_if<Cond,T>::type
is a template expression
that will instantiate to T
when Cond
is true
and will instantiate to nothing when
Cond
is false.
That is correct when Cond
is true
, because std::enable_if<true,T>
is defined like:
std::enable_if<true,T> {
typedef T type;
};
But std::enable_if<false,T>
is defined like:
std::enable_if<false,T> {};
See those definitions here
Hence std::enable_if<false,T>::type
does not disappear; for any T
,
it becomes an ill-formed, uncompilable, reference to the member-type type
of
std::enable_if<false,T>
, which does not exist. It becomes a compiler error.
Thus std::enable_if
can be used to make instantiation of a template class or
function either possible or impossible based upon the compiletime value of some
boolean constant. And since the compiler will dismiss from consideration any
non-viable instantiation of a template, provided there is a viable alternative,
it can therefore be used to make a compiletime choice between alternative
implementations of a template class or function, based on such a condition, e.g.
template<bool Cond>
typename std::enable_if<Cond,int>::type foo(int i) // A
{
return i*i;
}
template<bool Cond>
typename std::enable_if<!Cond,int>::type foo(int i) // B
{
return i + i;
}
Here, foo<some_bool_constant>(i)
will yield i squared if some_bool_constant
is true
and
i doubled if some_bool_constant
is false
, because in the first case
the B
overload has an ill-formed instantiation and in the second case
it's the A
overload that becomes ill-formed.
The alternative overloads give the compiler a choice of instantiations to consider
each time it sees foo<some_bool_constant>(x)
, with one turning out ill-formed and the other viable,
depending on the truth-value of some_bool_constant
. It picks the one that is viable in each case. This
behaviour is called SFINAE, and that
is what you need to understand before you can understand std::enable_if
.
But there are no alternative instantiations of your:
q <int, int, enable_if<false, bool>::type> ()
"depending on the value of false". false unconditionally means false and so this is just ill-formed code.
And consider: Even if std::enable_if<false,bool>::type
did somehow disappear, that would leave:
q <int, int, > ()
which is still ill-formed.
What could I do to correct my example and make it work?
It doesn't take much, in fact, and std::enable_if
does not feature. Let's address the real question: How can my template use or
ignore a template argument, based only a compiletime condition?
You need to define your template q<First, ... Rest>
and some other template accept_if<bool Cond, typename T>
in such a way
that for any instantiation of q
:
/*(Iq)*/ q<A0,....,Aj,....,Ak> // 0 <= j <= k
where Aj
= accept_if<some_bool_constant,t>::type
, then if some_bool_constant
== true
,
(Iq) will have exactly the same effects as:
q<A0,....,t,....,Ak>
and if some_bool_constant
== false
then
(Iq) will have the same effects as:
q<A0,....,Ak>
without resorting to any runtime test of some_bool_constant
. (In (Iq),
the '....' are just schematic place-holders for possible arguments: nothing to do
with C++ packs or elipses.)
But accept_if<some_bool_constant,t>::type
must resolve to some type specifier at compiletime
or the code is ill-formed. It cannot resolve to nothing. So in the false
case it must resolve to a type
specifier that the instantiation of (Iq) will handle as specifying no data-type.
Now the compelling utility of possessing a type specifier that specifies no data-type
in C/C++ was recognized as long ago as the very first ANSI Standard for C. That
type specifier is void
.
From your comments we learn that the template parameters of q
represent a sequence of data types which an instantiation of the constructor shall extract
from a source passed by argument. In that case, void
ideally fits the bill
as a type specifer denoting nothing to be done. You cannot declare a void
object,
let alone extract one from somewhere.
Hence I suggest that your accept_if
be defined:
template<bool Cond, typename T>
using accept_if = std::conditional<Cond,T,void>::type;
i.e. accept_if<true,T>
= T
and accept_if<false,T>
= void
. Read about
std::conditional
With this definition, you then have only to add to your existing specializations
of q
one more:
template<> struct q<void> {};
giving q<void>
a default constructor that does nothing. Here is a sample program
like your own, but well-formed, that illustrates this solution:
#include <type_traits>
#include <iostream>
template<bool Cond, typename T>
using accept_if = typename std::conditional<Cond,T,void>::type;
template <typename first, typename... params> struct q;
template<> struct q<void> {};
template <typename first> struct q <first>
{
q()
{
std::cout << "x" << std::endl;
}
};
template <typename first, typename... params> struct q
{
q()
{
std::cout << "x";
q<params...>();
}
};
int main()
{
q<int, int>();
q<int, int, accept_if<true,bool>>();
q<int, int, accept_if<false,bool>>();
return 0;
}
The output is what you wanted:
xx
xxx
xx
In a different scenario it is possible that you would want even Aj
= void
not to be ignored in (Iq). But since we can create types at will, you can
always a create a type that is by fiat different from all those that your template
must not ignore, say: struct ignore {};
Then you would define:
template<bool Cond, typename T>
using accept_if = std::conditional<Cond,T,ignore>::type;
and specialize:
template<> struct q<ignore> {};