I'm trying to understand enable_if and SFINAE. So far, my use of templates has been explicitly passed template arguments to the template parameters or deduction of template parameters during instantiation. I'm confused in the code below how the member of enable_if<>::type (typedef int type) can be a template parameter. I.e. the parameter list becomes <class T = int, int* = nullptr>. I would expect a parameter like this to be int Z = nullptr.
Does anyone know what I'm missing in regards to understanding why these template parameters assigned by enable_if work?
Thanks
#include <type_traits>
#include <iostream>
#if 1
template <class T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void do_stuff(T& t) {
std::cout << "do_stuff integral\n";
}
#endif
#if 0 // understood
template <class T>
typename std::enable_if<std::is_integral<T>::value,
void>::type
do_stuff(T& t) {
std::cout << "do_stuff integral\n";
}
#endif
struct S {};
int main(int argc, char *argv[])
{
int i = 1;
do_stuff(i);
//do_stuff<S>(S{});
return 0;
}
There are two kinds of template parameters:
type template parameters
non-type template parameters
The name of the template parameter is optional
A template parameter can have a default value
Type template parameter
named, non-defaulted
template <class T>
struct X {};
Usage:
X<int> x{};
named, defaulted
template <class T = int>
struct X {};
Usage:
X<> x1{};
X<char> x2{};
unnamed non-defaulted
template <class>
struct X {};
Usage:
X<int> x{};
unnamed, defaulted
template <class = int>
struct X {};
Usage:
X<> x1{};
X<char> x2{};
Non-type template parameter
named, non-defaulted
template <int I>
struct X {};
Usage:
X<24> x{};
named, defaulted
template <int I = 24>
struct X {};
Usage:
X<> x1{};
X<11> x2{};
unnamed, non-defaulted
template <int>
struct X {};
Usage:
X<11> x{};
unnamed, defaulted
template <int = 24>
struct X {};
Usage:
X<> x1{};
X<11> x2{};
For SFINAE the technique requires a default value. Since you don't care for the parameter inside the class you can skip its name. As for the type or non-type you can chose which one is easier to write in your particular case.
The parameter is anonymous. The type and default is specified, but there's nothing to actually refer to it later.
template<typename> struct A; // fine
template<int> struct B; // fine
template<typename F, F = nullptr> struct C; // fine
using AI = A<int>;
using BI = B<5>;
using CI = C<int, 5>;
using CD = C<void*>; // all fine
using Oops = C<char>; // not fine
Your case just replaces the simpler types like int and F with a big honking typename std::enable_if<...>::type. We don't care about the actual argument that gets passed it ends up being, so that's why it's unnamed.
template <class T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void do_stuff(T &t) {
std::cout << "do_stuff integral\n";
}
Plugging in T = int, typename std::enable_if<std::is_integral<T>::value>::type = void, so the second defaulted parameter just looks like void* = nullptr, which means it's an unnamed parameter of type void* that defaults to nullptr. Note that the typename is part of the type of a non-type template parameter. It is not serving in its other capacity as the introducer of type template parameters. Also, please note that the modern C++ way to write this is
// typename instead of class is more a style thing
template<typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
where enable_if_t and is_integral_v are aliases that abbreviate all the ::value and typename ...::type nonsense:
namespace std {
template<typename T>
constexpr inline bool is_integral_v = std::is_integral<T>::value;
template<bool B, typename T = void>
using enable_if_t = typename std::enable_if<B, T>::type;
}
Related
I have come across the following code snippet:
template <typename T, typename = void>
struct test {
int t = sizeof(T);
};
I know that in typename = void, void is a default argument but it doesn't have a name! What is it useful for and what does it even mean?
This is used for specializations in conjunction with SFINAE. Doing this allows you to have code like
template <typename T, typename = void>
struct test {
int t = sizeof(T);
};
template <typename T>
struct test<T, std::enable_if_t<std::is_integral_v<T>>> {
// ^^ this part "fills in" the void ^^
int t = 42;
};
template <typename T>
struct test<T, std::enable_if_t<std::is_floating_point_v<T>>> {
// ^^ this part "fills in" the void ^^
int t = 21;
};
int main()
{
test<int> i;
std::cout << i.t << "\n";
test<double> d;
std::cout << d.t;
}
which outputs
42
21
Without the typename = void, we would not be able to add these specializations because there would be no second parameter the enable_if_t part could "fill in".
I personally like to see it as a case of default template (type)argument to sfinae out overloads which does not meet certain criteria.
First thing first, giving name to a default template argument is just fine, so the following is correct:
template <typename T, typename sometype= void>
struct test {
int t = sizeof(T);
};
In the above case , clearly the type argument sometype is not used anywhere in the struct test. But what if instead of setting default value to void, we set it using some compile time conditions so that the template function is only valid for integral types like so ?
(borrowing code's from nathan's answer)
template <typename T>
struct test<T, typename sometype = std::enable_if_t<std::is_integral_v<T>>> {
int t = 42;
};
If the T has type integral then sometype is defined otherwise the given template is ignored making use of sfinae.
Additionally, you can drop "sometype" to write :
template <typename T>
struct test<T, typename = std::enable_if_t<std::is_integral_v<T>>> {
int t = 42;
};
Finally compare this with the default values used in function declarations:
void foo(int = 9); //Function declaration can have default values without names too.
void foo (int a )
{
//some code
}
According to the reference, the name of a non-type template parameter is optional, even when assigning a default value (see (1) and (2)). Therefore these template structs are valid:
template <int> struct Foo {};
template <unsigned long = 42> struct Bar {};
I haven't seen a possibility of accessing the values of the non-type parameters.
My question is: What's the point of unnamed/anonymous non-type template parameters? Why are the names optional?
First, we can split declaration from definition.
So name in declaration is not really helpful. and name might be used in definition
template <int> struct Foo;
template <unsigned long = 42> struct Bar;
template <int N> struct Foo {/*..*/};
template <unsigned long N> struct Bar {/*..*/};
Specialization is a special case of definition.
Then name can be unused, so we might omit it:
template <std::size_t, typename T>
using always_t = T;
template <std::size_t ... Is, typename T>
struct MyArray<std::index_sequence<Is...>, T>
{
MyArray(always_t<Is, const T&>... v) : /*..*/
};
or used for SFINAE
template <typename T, std::size_t = T::size()>
struct some_sized_type;
What's the point of unnamed/anonymous non-type template parameters?
I can think of specialization:
template<int = 42>
struct Foo{
char x;
};
template<>
struct Foo<0> {
int x;
};
template<>
struct Foo<1> {
long x;
};
Then:
Foo<0> a; // x data member is int
Foo<1> b; // x data member is long
Foo<7> c; // x data member is char
Foo<> d; // x data member is char
Oh, you can access them!
template <int> struct Foo {};
template <int N>
int get(Foo<N>) {
return N;
}
int main() {
Foo<3> foo;
return get(foo);
}
This might be a bit contrived. But in general for some templates you don't want to name them and then it is convenient that you don't have to.
Unamed type and non-type parameters also allow you to delay type instanciation, using template-template parameters.
Take destination_type in the function below for instance.
It can resolve to any type that has a template-type parameter, and 0 to N template-values parameters.
template <template <typename, auto...> typename destination_type, typename TupleType>
constexpr auto repack(TupleType && tuple_value)
{
return [&tuple_value]<std::size_t ... indexes>(std::index_sequence<indexes...>) {
return destination_type{std::get<indexes>(tuple_value)...};
}(std::make_index_sequence<std::tuple_size_v<TupleType>>{});
}
static_assert(repack<std::array>(std::tuple{1,2,3}) == std::array{1,2,3});
Such mechanic comes handy when you need an abstraction on parameters-pack.
Here, for instance, we do not care if Ts... is a parameter-pack containing multiple arguments, or expand to a single type which itself has multiples template parameters.
-> Which can be transposed from template-type parameters to template-value parameters.
See gcl::mp::type_traits::pack_arguments_as_t
Complete example available on godbolt here.
template <template <typename ...> class T, typename ... Ts>
class pack_arguments_as {
template <template <typename...> class PackType, typename... PackArgs>
constexpr static auto impl(PackType<PackArgs...>)
{
return T<PackArgs...>{};
}
template <typename... PackArgs>
constexpr static auto impl(PackArgs...)
{
return T<PackArgs...>{};
}
public:
using type = decltype(impl(std::declval<Ts>()...));
};
template <template <typename ...> class T, typename ... Ts>
using pack_arguments_as_t = typename pack_arguments_as<T, Ts...>::type;
namespace tests
{
template <typename... Ts>
struct pack_type {};
using toto = pack_arguments_as_t<std::tuple, pack_type<int, double, float>>;
using titi = pack_arguments_as_t<std::tuple, int, double, float>;
static_assert(std::is_same_v<toto, titi>);
static_assert(std::is_same_v<toto, std::tuple<int, double, float>>);
static_assert(std::is_same_v<pack_type<int, double, float>, pack_arguments_as_t<pack_type, toto>>);
}
After reading this question I had to realize once more how little I know about templates. I can understand, that a template specialization like this
// A
template <typename T> void foo(T x){}
template <> void foo<double>(int x){}
cannot work (error: template-id 'foo<double>' for 'void foo(int)' does not match any template declaration). Not only would it make little sense, but also parameter deduction would have no chance to get the right T. However, I do not understand why it does not work for return types:
// B
template <typename T> int foo(T x){}
template <> double foo<double>(double x){}
(similar error as above). Actually I dont have any particular use case at hand, but still I would be interested in how to choose a return type depending on T. As a workaround I found this:
// C
template <typename T> struct foo { static int moo(T x){return x;} };
template <> struct foo<double> { static double moo(double x){return x;} };
So it is possible to choose the return type depening on T. However, I am still puzzled...
What is the reason for B being not possible?
Even if strange, you may have
template <typename T>
void foo(int);
template <typename T>
char foo(int);
Demo
So your specialization would be ambiguous.
Actually, you can work around it either by using a template parameter for the return type or a traits class.
As an example:
#include<type_traits>
template<typename T>
T f() { return T{}; }
template<typename>
struct Traits;
template<>
struct Traits<int> {
using ReturnType = char;
};
template<>
struct Traits<char> {
using ReturnType = int;
};
template<typename T>
typename Traits<T>::ReturnType g() { return T{}; }
int main() {
static_assert(std::is_same<decltype(f<int>()), int>::value, "!");
static_assert(std::is_same<decltype(f<double>()), double>::value, "!");
static_assert(std::is_same<decltype(g<int>()), char>::value, "!");
static_assert(std::is_same<decltype(g<char>()), int>::value, "!");
}
In the case of g, if parameters are deduced you have that two apparently identical calls have different return types.
That said, specializations don't allow the user to change the declaration of a function.
That's why you have to play with such a definition to have different return types for different template arguments.
1. Default template parameters
Your case "C" may be easily worked around with default template parameters:
template<typename T, typename U = int>
U foo(T x) {
return x;
}
template<>
double foo<double, double>(double x) {
return x;
}
Now foo may be used like a function with single template argument:
auto a = foo(5);
auto b = foo(1.0);
auto c = foo<short>(5);
2. Type map
Another approach is much uglier, but somewhat more universal. It will require you to enumerate all possible return types in one place, allowing you to choose any of those return types based on template parameter type. The crux of this solution is just a compile-time type map. It can be implemented variously using pairs, tuples, and tuple_elements, but let's stop on a minimalistic implementation:
struct last_key{};
struct last_value{};
template<typename key, typename k = last_key, typename v = last_value, typename ...pairs>
struct type_map {
static_assert(sizeof...(pairs) % 2 == 0, "Last key does not have a value");
using type = typename std::conditional<std::is_same<key, k>::value,
v,
typename type_map<key, pairs...>::type>::type;
};
template<typename key>
struct type_map<key> {
using type = int;
};
The map "returns" value type for a given key type or int if key not found. Having the map, let's declare a return type which depends on a single template parameter:
template<typename key>
using return_type = typename type_map<key, double, double>::type;
And finally your example case "C" will again be solved by the only declaration of foo:
template<typename T>
auto foo(T x) -> return_type<T> {
return x;
}
But if you want, you still may add specialization with different behavior, which will compile and work correctly:
// Specialization.
template<>
auto foo(double x) -> double {
return x;
}
Now both with or without specialization, the following code:
auto a = foo(1);
auto b = foo(1.0);
std::cout << std::is_same<decltype(a), int>::value << std::endl;
std::cout << std::is_same<decltype(b), double>::value << std::endl;
will print
1
1
The following code compiles. Anyone can explain why? I've been digging the standard to figure out why it's legal.
template <bool B, typename T = void> struct enable_if { };
template <typename T> struct enable_if<true, T> {
typedef T type;
};
template <typename T, typename Enable = void> struct A;
template <typename T> struct A<T, typename enable_if<(sizeof(T) <= ~0ULL)>::type> {
void f() { }
};
int main() {
A<int> a;
a.f();
}
At the statement:
A<int> a;
As there's only one template paramerter "int", the compiler should go to use the primary template, which is:
template <typename T, typename Enable = void> struct A;
which is undefined, thus causing an error.
From § 14.5.5.1
1 When a class template is used in a context that requires an
instantiation of the class, it is necessary to determine whether the
instantiation is to be generated using the primary template or one of
the partial specializations. This is done by matching the template
arguments of the class template specialization with the template
argument lists of the partial specializations.
— If exactly one matching specialization is found, the instantiation is generated from
that specialization.
Let's try to figure out what's going on here:
// definition of enable_if, second parameter is defaulted to void
template <bool B, typename T = void>
struct enable_if { };
// specialization of enable_if, if first parameter is true,
// enable_if has a typedef for the second parameter
template <typename T>
struct enable_if<true, T> {
typedef T type;
};
// definition of struct A, second parameter defaults to void
template <typename T, typename Enable = void>
struct A;
// specialization of struct A, second parameter
// is obtained from the enable_if::type typedef
// the first parameter of enable_if is true if the size of T
// is smaller than the max long long (~0 --> all F)
template <typename T>
struct A<T, typename enable_if<(sizeof(T) <= ~0ULL)>::type> {
void f() { }
};
int main() {
// So we attempt the specialization for struct A<int,enable_if...>
// The expression of enable_if evaluates to...
// (sizeof(int) <= ~0ULL) == true
// ... so it applies the specialization of enable_if<true,void>
// (second parameter is void because none is provided, so it
// uses the default.
// so the enable_if template is valid (selected the specialization)
// and that means that the struct A<int,enable_if> specialization
// is valid too, so it is selected.
A<int> a;
a.f();
}
The compiler uses the template A<int, enable_if<true>:::type > when you declare A<int> since sizeof(int) <= ~0ULL evaluates to true.
There is no problem with enable_if<true>::type because the compiler is able to use enable_if<true, true>::type.
When you consider your enable_if:
template <bool B, typename T = void> struct enable_if{ };
template <typename T> struct enable_if<true, T>
{
typedef T type;
};
in
void test_EnableIf
{
static_assert(
std::is_same<
enable_if<(sizeof(int) > 0)>::type,
void>::value, "test_EnableIf failed." );
}
the result (type) is void, as no type was specified (as second
template parameter). The specialization of enable_if is selected
because of the boolean expression being true, and the default
parameter is selected (from primary template) because no other was
provided, and hence type is void, but NOTE that the definition
of type does exist (as the specialization was selected).
Now, in your definition of A...
template <typename T, typename Enable = void> struct A;
template <typename T>
struct A<T, typename enable_if<
(sizeof(T) <= ~0ULL)>::type>
{
void f() { }
};
...because type does exist in enable_if, it is a better match, which causes the specialization to be selected, and hence compiles.
A trivial example which amounts to the same thing is the following:
template <class T, class U = void>
struct X;
template <class T>
struct X<T,void>
{
static int foo(){ return 0; }
};
int main()
{
return X<int>::foo();
}
Maybe I'm tired, but I'm stuck with this simple partial specialization, which doesn't work because non-type template argument specializes a template parameter with dependent type 'T':
template <typename T, T N> struct X;
template <typename T> struct X <T, 0>;
Replacing 0 by T(0), T{0} or (T)0 doesn't help. So is this specialization even possible?
See paragraph [temp.class.spec] 14.5.5/8 of the standard:
The type of a template parameter corresponding to a specialized
non-type argument shall not be dependent on a parameter of the
specialization. [ Example:
template <class T, T t> struct C {};
template <class T> struct C<T, 1>; // error
template< int X, int (*array_ptr)[X] > class A {};
int array[5];
template< int X > class A<X,&array> { }; // error
—end example ]
The answer to your edit: the easiest workaround is to replace a non-type template parameter with a type one:
#include <type_traits>
template <typename T, typename U>
struct X_;
template <typename T, T N>
struct X_<T, std::integral_constant<T, N>> {};
template <typename T>
struct X_<T, std::integral_constant<T, 0>> {};
template <typename T, T N>
struct X : X_<T, std::integral_constant<T, N>> {};
Solution using Yakk's solution:
#include <iostream>
#include <type_traits>
template <typename T, T N, typename = void >
struct X {
static const bool isZero = false;
};
template <typename T, T N>
struct X < T, N, typename std::enable_if<N == 0>::type > {
static const bool isZero = true;
};
int main(int argc, char* argv[]) {
std::cout << X <int, 0>::isZero << std::endl;
std::cout << X <int, 1>::isZero << std::endl;
return 0;
}
Live Demo
You can add a typename=void parameter to the end of the list of template arguments, then go hog wild with std::enable_if_t< condition > in specializations.
You need to pass an integral value in a template, Both, your first and second template, will not work if the type T is not an integral type.
You can pass Traits as a typed template parameter to specify the value N:
#include <iostream>
// error: ‘double’ is not a valid type for a template non-type parameter
template <typename T, T N> struct X0;
// error: ‘double’ is not a valid type for a template non-type parameter
template <typename T, T N, int = 0> struct X1;
template <typename T, T N>
struct IntegralTraits {
static constexpr T Value() { return N; }
};
template <typename T, typename Traits = void>
struct X2 {
static constexpr T Value() { return Traits::Value(); }
};
template <typename T>
struct X2<T, void> {
static constexpr T Value() { return T(); }
};
int main() {
// error: ‘double’ is not a valid type for a template non-type parameter
// X0<double, 0>();
// error: ‘double’ is not a valid type for a template non-type parameter
// X1<double, 0>();
X2<int> a;
X2<double, IntegralTraits<int, 1>> b;
std::cout.precision(2);
std::cout << std::fixed << a.Value() << ", "<< b.Value() << '\n';
return 0;
}
If you limit yourself to integral types pick a large one:
template <typename T, std::size_t N = 0> struct X {};