I've got a templated class that uses a template parameter (std::enable_if_t
) to restrict the main template parameter as shown here:
template <typename T, typename
= std::enable_if_t<std::is_arithmetic_v<T>>>
class Foo
{
void foo();
}
template<typename T>
void Foo<T>::foo() {} // compiler complains
I know I could work around this using static_assert
, but I'd like to know what the correct approach would be in this case.
I suppose I could simply copy both template parameters again, but that seems very odd to me.
You have to add the second template parameter:
// ..................vvvvvvvvvv
template<typename T, typename U>
void Foo<T, U>::foo() {} // compiler doesn't complains anymore
// ........^^
or, maybe better, implement the method inside the class:
template <typename T, typename
= std::enable_if_t<std::is_arithmetic_v<T>>>
class Foo
{
void foo() {}
// .......^^^
};
Anyway... take into account that your way to use SFINAE to restrict the type of the (first) parameter has a defect: you are operating over the default value of the second template parameter, so it can be bypassed by explicitly including the second template parameter in instantiations.
I mean... you can correctly define a type when T
is arithmetic:
Foo<int> f; // compile
and correctly doesn't compile when T
isn't arithmetic:
Foo<void> f; // compilation error
But, it compiles (when T
isn't arithmetic) when you explicitly include a second template parameter:
Foo<void, void> f; // compiles!!!
I suppose you don't wont that Foo<void, void>
to compile.
To avoid this problem, you can use SFINAE on the left side of the equal sign, deleting the template parameter, not the default value.
For example, you can write:
template <typename T,
std::enable_if_t<std::is_arithmetic_v<T>, bool> = true>
class Foo
{
void foo() {}
};
This way, you're sure nobody can instantiate a Foo
object when the first template parameter isn't an arithmetic type.
This solution has a little problem (or maybe not... depends on what you expect): you can define two different classes over the same T
parameter.
I mean... you can define:
Foo<int> f0;
Foo<int, true> f1; // same type as f0
Foo<int, false> f2; // different type from f0 and f1
To avoid this, for the type of the second parameter, you can use a type that accepts a single value. For example: std::nullptr_t
template <typename T,
std::enable_if_t<std::is_arithmetic_v<T>, std::nullptr_t> = nullptr>
class Foo
{
void foo() {}
};
Now, you can have a Foo<int>
, that is shorthand for Foo<int, nullptr>
, but not a class that differs in the second template parameter.