I play around with template specialization and SFINAE.
As for the following example, the things seems easy:
template <class T>
void Do(T t, typename std::enable_if<std::is_integral<T>::value >::type* = 0)
{
cout << "is integer" << endl;
}
template <class T>
void Do(T t, typename std::enable_if<std::is_floating_point<T>::value >::type* = 0)
{
cout << "is float" << endl;
}
No I tried std::is_array, but the specialization with std::is_array is never used.
So I tried out why is_array never matches:
template <int num>
void Do( int a[num])
{
cout << "int array size " << num << endl;
}
void Do( int* x)
{
cout << "int*" << endl;
}
...
int e[] = { 1,2,3 };
Do(e);
...
The first mystery for me is, that the specialization with "int a[num]" did never catch! The function parameter always has the type int*.
If I use reference types I got the "correct" result:
template <int num>
void Do( int (&a)[num])
{
cout << "int array size " << num << endl;
}
void Do( int* &x)
{
cout << "int*" << endl;
}
So my question comes up: Is there a reasonable usage of std::is_array in combination with template function parameters? I know that
cout << boolalpha << std::is_array<decltype(e)>::value << endl;
will give me the correct result. But declaring the template selection manually gives me no functional add on. I there any way to detect (with or without SFINAE) that an template specialization from function parameters fits to an array?
I think you got it yourself - pass arrays to template functions by reference, if you want to use their type in the secialization.
The reason you want to do this is array-to-pointer decay, which is one of the few implicit conversions that happen to template function arguments before they are matched to the parameter types. That's why T was a pointer when you tried to check that it is an array type in DoIt. However, array-to-pointer decay does not happen when the target type is reference type. So, to sum up:
template <class T>
void Do(T& t, typename std::enable_if<std::is_array<T>::value >::type* = 0)
should work.
BTW the boring way of not using SFINAE
template <class T, unsigned N>
void Do(T (&t)[N])
works too.
Related
I have the following code:
// converters.h
#pragma once
#include <iostream>
#include <type_traits>
template <typename TTo, typename TFrom>
static TTo convert(const TFrom& from)
{
TTo t = static_cast<TTo>(from);
std::cout << "From type: " << typeid(TFrom).name() << "\nTo type: " << typeid(TTo).name();
return t;
}
template<typename TTo, typename TFrom>
static int convert(std::enable_if_t<std::is_enum_v<TFrom>, TFrom> const& from)
{
std::cout << "[X] From type: " << typeid(TFrom).name() << "\nTo type: " << typeid(TTo).name();
return 0;
}
// Main.cpp
#include "converters.h"
enum class Season
{
Spring,
Summer,
Autumn,
Winter
};
int main()
{
convert<int>(Season::Spring);
return 0;
}
I want to add some constraints to TFrom or TTo or both as function template specialization, but somehow it's not working. The more stricter version of the function template specialization is not getting called.
In the above example, I expect the second one is getting called, however, it is still calling the primary one.
How can I add constraints to function template specialization?
What if TTo or TFrom is STL types, like std::unordered_map<TKey, TVal>? What I want to do is to convert from any type to the other type with this convert function call. If the primary template doesn't meet the need, then just add a new specialization. Anyone can give some guidance on how to achieve this purpose? Thanks. Any input is appreciated.
Note: I know some c++20 concept can add constraints, but let's assume it's c++17 due to some reason.
In this function declaration:
template<typename TTo, typename TFrom>
static int convert(std::enable_if_t<std::is_enum_v<TFrom>, TFrom> const& from);
type TFrom appears in a non-deduced context. So, when you call:
convert<int>(Season::Spring);
the compiler can only call (due to SFINAE)
template <typename TTo, typename TFrom>
static TTo convert(const TFrom& from);
You may get the behavior you expect by SFINAE-ing on the return type, i.e.:
template <typename TTo, typename TFrom>
static std::enable_if_t<!std::is_enum_v<TFrom>, TTo> convert(TFrom const& from) {
TTo t = static_cast<TTo>(from);
std::cout << "From type: " << typeid(TFrom).name()
<< "\nTo type: " << typeid(TTo).name();
return t;
}
template <typename TTo, typename TFrom>
static std::enable_if_t<std::is_enum_v<TFrom>, int> convert(TFrom const& from) {
std::cout << "[X] From type: " << typeid(TFrom).name()
<< "\nTo type: " << typeid(TTo).name();
return 0;
}
Notes:
You need to constrain the return type on both the function templates, otherwise convert<int>(Season::Spring) would result in an ambiguous overload call;
Function templates cannot be partially specialized (even though you're not partially specializing it, anyway);
As noted in the comments, there's no need for typename TTo in the second version of convert, if you always return int.
In the following code yields (LiveExample):
Class template pointer
Class template pointer
Template pointer
Template array with size 10
#include <iostream>
#include <utility>
#include <type_traits>
template <class TVar>
class CVar
{
public:
void classTemplate(TVar *) const
{
std::cout << "Class template pointer\n";
}
template<unsigned sz>
void classTemplate(TVar(&)[sz]) const
{
std::cout << "Class template array with size " << sz << "\n";
}
};
template<typename T>
void regTemplate(T)
{
std::cout << "Template pointer\n";
}
template<typename T, unsigned sz> void regTemplate(T(&)[sz])
{
std::cout << "Template array with size " << sz << "\n";
}
int main()
{
unsigned int test[10] = {};
CVar<unsigned> *cFoo = new CVar<unsigned>();
cFoo->classTemplate(&test[0]);
cFoo->classTemplate(test);
regTemplate(&test[0]);
regTemplate(test);
}
Why when I overload the template in the class, it cannot resolve the desired functionality - that is, cFoo->classTemplate(test); is called, then the response will be Class template array with size 10?
How do I achieve my desired result in 1 without changing cFoo->classTemplate(test);? Note that void CVar<TVar>::classTemplate(TVar(&)[sz]) const may change if needed
Other things equal, overload resolution prefers non-templates over templates. In classTemplate case, one overload is a function template while the other is a non-template member function.
In regTemplate case, both are function templates: the one taking an array is chosen because it's more specialized. Indeed, if you change the first overload to take T*, the call becomes ambiguous.
I am trying to create a templated function is compile-time enforced to use only specializations. I referenced Force a compile time error in a template specialization which suggests to use a static_assert on something inherited from std::false_type.
#include <iostream>
using namespace std;
template<typename T>
struct always_false : std::false_type {};
//Case: Default
template<typename T>
void foo(T val) {
static_assert(always_false<T>::value, "");
}
//Case: bool
template<>
void foo<bool>(bool val) {
cout << "Is explicitly a bool! " << val << endl;
}
//Case: int
template<typename T, typename std::enable_if<!std::is_same<T,bool>::value && std::is_convertible<T,int>::value,int>::type=0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}
int main() {
foo(true); //(Good) Works correctly
foo((int)5); //(Bad) Error: call of overload foo(int) is ambiguous
foo((unsigned int)10); //(Bad) Error: call of overload foo(unsigned int) is ambiguous
foo((void*)nullptr); //(Good) Error: static assertion failed
return 0;
}
When I pass in an int or unsigned int, the compiler complains that the call is ambiguous suggesting that it can use either Case: Default or Case: int.
This is confusing as the Case: Default has the always_false static_assert() and I would expect the compiler to disallow it.
My last example passing in a void* successfully triggers the static_assert() and causes a compile-time error.
I am new to programming using SFINAE template metaprogramming, so I suspect I am doing something wrong in the Case: int specialization
Two questions:
Why is foo(int) in this code ambiguous?
Is there a better way to use
templates to get this desired behavior (explicit bool specialization + implicit integers specialization)?
Why is foo(int) in this code ambiguous?
Because the version with static_assert() give error if selected but still exist; so the compiler doesn't know if choose the generic version or the integer enabled version.
Is there a better way to use templates to get this desired behavior (explicit bool specialization + implicit int specialization)?
A possible way is to avoid the generic version and SFINAE enable the version you need
The following is a full working example
#include <iostream>
#include <type_traits>
template <typename T>
typename std::enable_if<std::is_same<T, bool>::value>::type foo(T val)
{ std::cout << "bool case " << val << std::endl; }
template <typename T>
typename std::enable_if< ! std::is_same<T, bool>::value
&& std::is_convertible<T, int>::value>::type foo(T val)
{ std::cout << "integer case " << (int)val << std::endl; }
int main()
{
foo(true); // bool case
foo(1); // integer case
foo(2U); // integer case
foo(3L); // integer case
foo(4UL); // integer case
foo(5LL); // integer case
foo(6ULL); // integer case
// foo((void*)nullptr); // compilation error
}
-- EDIT --
The OP
Sorry, I am still confused. Could you elaborate? I thought that due to SFINAE, that if an error occurred in substitution, it would use the other template.
Exactly.
The problem is when there isn't an error in substitution and the compiler have to choose between two different version of the same template.
I mean: in your example, when you call foo(5), there isn't error in substitution of
typename std::enable_if<!std::is_same<T,bool>::value
&& std::is_convertible<T,int>::value,int>::type=0>
So the compiler have to choose between the two template functions
template<typename T>
void foo(T val) {
static_assert(always_false<T>::value, "");
}
//Case: int
template<typename T, int = 0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}
that differ only for a template value with a default value, so are (from the compiler point of view) indistinguishable.
And observe that
template<>
void foo<bool>(bool val) {
cout << "Is explicitly a bool! " << val << endl;
}
is a (full) template specialization but
//Case: int
template<typename T, int = 0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}
isn't a template specialization (no partial template specialization of a function is admitted in C++11/14/17; you can partial specialize only structs/classes); is a generic template.
You could use SFINAE as suggested by #max66, but a simple way for your use case would be to have a bool overload and a templated version
void foo(bool);
template <class T>
void foo(T);
You can enforce that T is convertible to int (static_assert) but in most cases it is not necessary because the body of foo will probably be ill-formed in such case, thus leading to a compile-time error.
template <class T>
void foo(T) {
static_assert(std::is_convertible<T, int>::value, "");
}
With your examples:
foo(true); // foo(bool) is chosen because it is the best match
foo((int)5); // foo<int>(int) is chosen, the assertion passes
foo((unsigned int)10); // foo<unsigned int>(unsigned int) is chosen, assertion ok
foo((void*)nullptr); // foo<void*>(void*) is chosen, the assertion fails
Let's say I have a function that takes a gsl::span of an arbitrary type (templated).
template <class T>
void foobar(gsl::span<T> x)
{
cout << "generic" << endl;
}
I'd like to create a template specialization/function overload, such that when I have a gsl::span<float> or gsl::span<const float> as argument, that function is used.
template <>
void foobar(gsl::span<float> x) // or gsl::span<const float>
{
cout << "specialization" << endl;
}
If possible, I would not like to write two specializations for float and const float, but catch them both with the same function. I have functions that take two or three spans, and the amount of specializations to write would grow exponentially.
I would also like to have the same kind of specializations for double, so I doubt whether std::is_floating_point<> would be refined enough. I've tried working with std::is_same<std::decay_t<T>, float>, but then calls become ambiguous.
Is this possible?
You may use overload and SFINAE...
template <typename T>
std::enable_if_t<!std::is_floating_point<std::decay_t<T>>::value>
foobar(gsl::span<T> x)
{
std::cout << "generic" << std::endl;
}
template <typename T>
std::enable_if_t<std::is_floating_point<std::decay_t<T>>::value>
foobar(gsl::span<T> x)
{
std::cout << "floating point specialization" << std::endl;
}
This question already has answers here:
SFINAE did not compile [duplicate]
(2 answers)
Closed 7 years ago.
The following code fails to compile because of error: redefinition of ‘template<class Integer, class> void func(Integer)’
#include <iostream>
#include <type_traits>
template<typename Float, typename = typename
std::enable_if<std::is_floating_point<Float>::value>::type>
void func(Float floatVal)
{
std::cerr << "float: " << floatVal << "\n";
}
template<typename Integer, typename = typename
std::enable_if<std::is_integral<Integer>::value>::type>
void func(Integer integer)
{
std::cerr << "integral: " << integer << "\n";
}
int main()
{
func(32.4246);
func(144532);
}
But the two functions will clearly have different signatures on template instantiation. So why is can't this compile?
Please note: I do know how to fix this: just adding another dummy template parameter to one of the functions, e.g. typename=void, will work, like here
template<typename Integer, typename dummy=void, typename = typename
std::enable_if<std::is_integral<Integer>::value>::type>
void func(Integer integer){}
But the question is why do I have to do this?
N4527 §1.3.19 [defns.signature.templ]
signature
<function template> name, parameter type list (8.3.5), enclosing namespace (if any), return type, and
template parameter list
The default template argument is not part of the signature of a function template.
You can change std::enable_if<...>::type as the return type of the function. As far as I know you cannot pass it to the type of another template parameter.
#include <iostream>
#include <type_traits>
template<typename Float>
typename std::enable_if<std::is_floating_point<Float>::value>::type
func(Float floatVal)
{
std::cerr << "float: " << floatVal << "\n";
}
template<typename Integer>
typename std::enable_if<std::is_integral<Integer>::value>::type
func(Integer integer)
{
std::cerr << "integral: " << integer << "\n";
}
int main()
{
func(32.4246);
func(144532);
}
Live Example
Alternatively to overloading on return type like NathanOliver did, you can be a little more complicated in the template types:
template<typename Float, typename std::enable_if<std::is_floating_point<Float>::value>::type* = nullptr>
void func(Float floatVal)
{
std::cerr << "float: " << floatVal << "\n";
}
template<typename Integer, typename std::enable_if<!std::is_floating_point<Integer>::value && std::is_integral<Integer>::value>::type* = nullptr>
void func(Integer integer)
{
std::cerr << "integral: " << integer << "\n";
}
Live Demo
Notice that the second enable_if for Integer explicitly negates the enable_if condition for Float
The benefit of this approach is that your functions still return void
And test it:
int main()
{
func(32.4246);
func(144532);
}
Output:
float: 32.4246
integral: 144532