Function template SFINAE
cpppatterns Function template SFINAE
#include <type_traits>
#include <limits>
#include <cmath>
template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
equal(T lhs, T rhs)
{
return lhs == rhs;
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
equal(T lhs, T rhs)
{
return std::abs(lhs - rhs) < 0.0001;
}
Requires
c++11 or newer.
INTENT
Conditionally instantiate a function template depending on the template arguments.
DESCRIPTION
We provide two implementations of the equal
function template:
- The template on lines 5–10 will only be instantiated when
T
is an integral type. - The template on lines 12–17 will only be instantiated when
T
is a floating point type.
We have used std::enable_if
on line 6 and line 13 to force instantiation to succeed only for the appropriate template arguments. This relies on Substitution Failure Is Not An Error (SFINAE), which states that failing to instantiate a template with some particular template arguments does not result in an error and simply discards that instantiation.
The second template argument of std::enable_if
— in this case, bool
— is what the full std::enable_if<...>::type
evaluates to when the first template argument is true
. This means that the return type of equal
will be bool
.
If you want to simply prevent a template from being instantiated for certain template arguments, consider using static_assert
instead.
NOTE: 上述解释和cpppatterns Class template SFINAE如出一辙。
Function template中SFINAE的作用对象
在class template中,SFINAE只能够作用于argument of type template parameter,在function template中,SFINAE可以作用于如下对象:
- SFINAE working in return type,cpppatterns Function template SFINAE就是这种写法
- SFINAE working in dummy template parameter
- SFINAE working in default argument of type template parameter
下面结合具体例子来对上述几种情况进行介绍:
SFINAE working in return type
cpppatterns Function template SFINAE就是这种写法,使用c++11的trailing return type的写法类似如下:
template<typename T>
auto foo(T)
-> typename std::enable_if<std::is_integral<T>::value>::type
{
std::cout << "I'm an integrer!\n";
}
template<typename T>
auto foo(T)
-> typename std::enable_if<std::is_floating_point<T>::value>::type
{
std::cout << "I'm a floating point number!\n";
}
上述代码取自:SFINAE working in return type but not as template parameter
SFINAE and Function template and trailing-return-type
在阅读redis-plus-plus的redis.h时,发现了类似于这样的语法
template <typename ...Args>
auto command(const StringView &cmd_name, Args &&...args)
-> typename std::enable_if<IsIter<typename LastType<Args...>::type>::value, void>::type;
SFINAE working in dummy template parameter
To fix the error move the enable_if
expression from default argument to a dummy template parameter
template <class T,
typename std::enable_if<std::is_floating_point<T>::value, int>::type* = nullptr>
bool less(T a, T b) {
// ....
}
template <class T,
typename std::enable_if<std::is_integral<T>::value, int>::type* = nullptr>
bool less(T a, T b) {
// ....
}
上述代码取自:https://stackoverflow.com/a/29502338
需要注意的是,上述第二个模板参数是一个non-type template parameter。
SFINAE working in default argument of type template parameter
这种用法是比较不好的,在下面文章中对这种写法进行了剖析:
- SFINAE working in return type but not as template parameter
- Template specialization and enable_if problems [duplicate]
样例程序1取自SFINAE working in return type but not as template parameter,如下:
template<typename T,
typename = typename std::enable_if<std::is_integral<T>::value>::type>
auto foo(T)
-> void
{
std::cout << "I'm an integer!\n";
}
template<typename T,
typename = typename std::enable_if<std::is_floating_point<T>::value>::type>
auto foo(T)
-> void
{
std::cout << "I'm a floating point number!\n";
}
样例程序2取自Template specialization and enable_if problems [duplicate],如下:
#include <type_traits>
#include <iostream>
template <class T,
class = typename std::enable_if<std::is_floating_point<T>::value>::type>
bool less(T a, T b) {
// ....
}
template <class T,
class = typename std::enable_if<std::is_integral<T>::value>::type>
bool less(T a, T b) {
// ....
}
int main() {
float a;
float b;
less(a,b);
return 0;
}
在回答中,给出的解释是比较清晰的:
You should take a look at
14.5.6.1 Function template overloading
(C++11 standard) where function templates equivalency is defined. In short, default template arguments are not considered, so in the 1st case you have the same function template defined twice. In the 2nd case you have expression referring template parameters used in the return type (again see 14.5.6.¼). Since this expression is part of signature you get two different function template declarations and thus SFINAE get a chance to work.
在Template specialization and enable_if problems [duplicate]中,也提供了回答。
Examples
Generic container printer
#include <iterator>
#include <type_traits>
// templates for determining whether the std::pair container element is
template<typename T>
struct is_pair: std::false_type
{
};
template<typename T, typename U>
struct is_pair<std::pair<T, U>> : std::true_type
{
};
template<typename T>
constexpr bool is_pair_v = is_pair<T>::value;
// templates for determining if the container is std::map
template<typename, typename = void>
struct is_mapping: std::false_type
{
};
template<typename Container>
struct is_mapping<Container, std::enable_if_t<is_pair_v<typename std::iterator_traits<typename Container::iterator>::value_type>>> : std::true_type
{
};
template<typename T>
constexpr bool is_mapping_v = is_mapping<T>::value;
#include <map>
#include <vector>
#include <iostream>
class ClassWithSpecializedMethods
{
public:
ClassWithSpecializedMethods()
{
}
// A specialized method for handling std::map
template<class ContainerType>
static typename std::enable_if<is_mapping_v<ContainerType>, void>::type printContainer(ContainerType &container);
// A specialized method for handling std::vector
template<class ContainerType>
static typename std::enable_if<!is_mapping_v<ContainerType>, void>::type printContainer(ContainerType &container);
};
// Method implementations
template<class ContainerType>
inline typename std::enable_if<is_mapping_v<ContainerType>, void>::type ClassWithSpecializedMethods::printContainer(ContainerType &container)
{
std::cout << "Map:" << std::endl;
for (const auto& [key, value] : container)
{
std::cout << "Key: " << key << " Value: " << value << std::endl;
}
std::cout << std::endl;
}
template<class ContainerType>
inline typename std::enable_if<!is_mapping_v<ContainerType>, void>::type ClassWithSpecializedMethods::printContainer(ContainerType &container)
{
std::cout << "Vector:" << std::endl;
for (const auto &value : container)
{
std::cout << "Value: " << value << std::endl;
}
std::cout << std::endl;
}
// Testing functions
int main()
{
std::cout << "is_pair:" << std::endl;
std::cout << "Map: " << is_pair_v<std::iterator_traits<std::map<int, int>::iterator>::value_type> << std::endl;
std::cout << "Vector: " << is_pair_v<std::iterator_traits<std::vector<int>::iterator>::value_type> << std::endl;
std::cout << std::endl;
std::cout << "is_mapping:" << std::endl;
std::cout << "Map: " << is_mapping_v<std::map<int, int>> << std::endl;
std::cout << "Vector: " << is_mapping_v<std::vector<int>> << std::endl;
std::cout << std::endl;
std::map<int, int> map_container = { { 1, 1 }, { 2, 2 }, { 3, 3 } };
std::vector<int> vector_container = { 1, 2, 3 };
ClassWithSpecializedMethods::printContainer(map_container);
ClassWithSpecializedMethods::printContainer(vector_container);
}
// g++ --std=c++17 -Wall -pedantic -pthread main.cpp && ./a.out
输出如下:
is_pair:
Map: 1
Vector: 0
is_mapping:
Map: 1
Vector: 0
Map:
Key: 1 Value: 1
Key: 2 Value: 2
Key: 3 Value: 3
Vector:
Value: 1
Value: 2
Value: 3