How to restrict typenames in template
Have a function which can accept 4 different types. And their implementations are very similar.
template< typename T >
void foo( std::string &&name, T value ){
//...
}
typename T
must be one of 4 predefined types. Other typenames is not acceptable, and should be restricted at compile-time.
Is it possible to write in C++?
I can think of at least three ways, off the top of my head.
First:
namespace internal {
template< typename T >
void foo_impl( std::string &&name, T value ) {
// actual implementation here
}
}
void foo(std::string &&name, SpecialType1 value ) {
internal::foo_impl(std::forward<std::string>(name), value);
}
void foo(std::string &&name, SpecialType2 value ) {
internal::foo_impl(std::forward<std::string>(name), value);
}
Second:
template< typename T >
typename std::enable_if<std::is_same<T, SpecialType1>::value ||
std::is_same<T, SpecialType2>::value >::type
foo( std::string &&name, T value ){
//implementation here
}
Third:
template< typename T >
void foo(std::string &&name, T value){
static_assert(std::is_same<T, SpecialType1>::value ||
std::is_same<T, SpecialType2>::value,
"wrong template argument");
//implementation here
}
Demo
<type_traits>
let you generalize your logic into class template. How this works is we take template parameter T
and parameter pack of Ts
and we start checking if T
is same to Head
of the list of types. If match is found, we inherit from std::true_type
and we are done. If no match is found, we pop the head and recursively instantiate same template with tail of Ts
. Eventually, if no match is found at all, parameter pack size will drop to zero and the compiler will instantiate base template class that inherit from std::false_type
. Please check this video for much better and in dept explanation by Mr. Walter E. Brown.
template<class T, class...> struct is_any_of: std::false_type{};
template<class T, class Head, class... Tail>
struct is_any_of<T, Head, Tail...>: std::conditional_t<
std::is_same<T, Head>::value,
std::true_type,
is_any_of<T, Tail...>>
{};
Now we can SFINAE over, using enable_if in almost English wording.
#include <type_traits>
#include <string>
template<
class T,
class = std::enable_if_t<is_any_of<T, int, float, unsigned, double>::value>
>
void foo(std::string &&str, T value) {}
int main()
{
foo(std::string{"hello"}, 3);
foo(std::string{"world"}, '0'); //compile-time error
}
SFANIE is a language feature, a tool, that's used or abused some say to achieve what you ask for,
The standard library component std::enable_if allows for creating a substitution failure in order to enable or disable particular overloads based on a condition evaluated at compile time. FYI http://en.cppreference.com/w/cpp/language/sfinae.
Note that std::conditional_t<>
and std::enable_if_t<>
are shorthanded from std::conditional<>::type
and std::enable_if<>::type
respectively. You could simple replace these in code, but should put typename
keyword before enable_if
then.
One thing I have seen people done is using std::enable_if
. I don't know exactly what your 4 types are, so this is an example with two types int
and float
.
using myType = std::enable_if<
std::is_same<int, T>::value ||
std::is_same<float, T>::value, T >::type;
myType
exists only if T
is exactly int
or float
. Hope that helps!
I know I am late to the party but maybe someone will find this useful.
As of C++20
one can use constraints and concepts to filter possible typename
s in a template.
The syntax goes like this:
template <typename T>
requires std::is_integral_v<T>
auto func(T any_integer) {
...
};
// Or, you can also do:
template <typename T>
concept Integer = std::is_integral_v<T>;
template <Integer T>
auto func(T any_integer) {
...
};
// Or, an even better approach:
auto func(Integer auto any_integer) {
...
};
I prefer the last one since it's nice and clean. But, that's just my opinion.
Following are some valid calls to func()
:
func(int{}); // OK!
func(long{}); // OK!
func(bool{}); // Yes, it's OK too!
func(char{}); // Yes, it's OK too!
Now, some invalid calls to func()
:
func(double{}); // Compiler error!
func(std::string{}); // Compiler error!
The compiler will also give a clean error message depending on your usage:
note: candidate: ‘template requires is_integral_v void func(T)’
or
note: candidate: ‘template requires Integer auto func(T)’
or
note: candidate: ‘template requires Integerauto:15 auto func(auto:15)’
References:
- https://en.cppreference.com/w/cpp/types/is_integral