[英]Difference between template specialization and SFINAE with std::enable_if?
如果我在 C++ 中有一个模板 function,并希望它在存在特定模板参数时以不同的方式表现,我将使用模板特化:
#include <iostream>
#include <type_traits>
template<typename T> void myFunction(T&& input) {
std::cout << "The parameter is " << input << '\n';
}
template<> void myFunction<int>(int&& input) {
std::cout << "Double of the parameter is " << 2 * input << '\n';
}
int main() {
myFunction(24.2);
myFunction(24);
myFunction("Hello");
}
使用<type_traits>
中的std::enable_if<bool B>
可以激活某些特定模板类型的类似行为。 任何其他类型都会导致替换错误。
#include <iostream>
#include <type_traits>
template<typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr >
void myFunction(T&& input) {
std::cout << "Double of the parameter is " << 2 * input << '\n';
}
template<typename T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr >
void myFunction(T&& input) {
std::cout << "The parameter is " << input << '\n';
}
int main() {
myFunction(24.2);
myFunction(24);
// myFunction("Hello"); // compile-time substitution error
}
我的问题是:使用更高级的std::enable_if
专业化代替标准模板专业化的真正好处是什么? 它们真的是替代品,还是std::enable_if
专门用于模板元编程?
使用std::enable_if<>
,您可以编写任意条件。 一个例子是根据 object 的大小进行专门化; 其他示例包括字段/成员 function 是否存在, static constexpr
function 的结果是否符合预期。 专业化仅限于类型匹配。
这两种方法在不同的阶段执行。 首先是重载决议——在这个阶段不使用模板特化。 仅使用通用模板 function 原型。
另一方面,SFINAE 方法丢弃了未能在此阶段静默替换的重载,仅根据 enable_if 条件留下一个候选者。
第二步是模板实例化——这里创建了 function 的主体——它将使用大多数专业模板提供的主体。
tl;博士
SFINAE 还有助于滥用模板实体导致更多可读错误。
有点长
不使用 SFINAE 而是仅仅依赖特化意味着,除其他外,您将留下一个无限制的主模板可用于重载解决方案,如果它获得了实现不提供的类型,这可能会导致非常冗长且不易阅读的错误没道理。
实际上,由于模板类型推导,模板将始终被实例化,但是,如果该类型碰巧不适合实现该 function。 这可能会在几个嵌套的 function 调用中发现。
正如在另一个答案中所指出的,SFINAE 允许人们在重载决议实际发生之前丢弃一些重载(有人说,SFINAE 将它们排除在外),希望留下更少的重载,以使程序员能够阅读更短的错误。
例子
这是一个与现实生活中的示例相距不远的示例,说明模板错误可能很长,但由于 SFINAE 而变得更短。
在下面的玩具示例中,我定义了一个模板 function 用于在集合中插入元素。 一些细节可以更好地理解我为什么要这样写:
在std::unordered_set<T, whatever>
中插入元素要求operator==
在两个T
类型的 arguments 上定义; 正如您从评论的friend
function 中看到的那样,我故意没有为类型A
定义它,以便使代码在编译时失败(如果您取消注释该行代码编译并运行顺利)。
friend
定义。 我定义了一个Equatable
概念来检查是否在给定类型T
的两个 arguments 上定义了operator==
,即该类型是否适合unordered_set
的元素类型; 这是有一个谓词应用于类型作为enable_if
的条件。
myInsert
是问题的 function object,模板 function 不受限制。
B
编写了一个特化,在该 B上定义了operator==
,但重点是在所有其他类型中您可以抛出它,有A
,在其上未定义operator==
,并且这导致了错误。#include <cmath>
#include <type_traits>
#include <unordered_set>
// concept to detect whether operator== is defined argument of a type T
template<typename T, typename = void>
struct Equatable : std::false_type {};
template<typename T>
struct Equatable<T, std::void_t<decltype(T{} == T{})>> : std::true_type {};
template<typename T>
constexpr bool equatable_v = Equatable<T>::value;
// The problematic class which has no operator== defined on it
struct A {
void empty(int) {
}
//friend bool operator==(A const&, A const&) { return true; }
};
// Our template function not using SFINAE
template<typename Coll, typename Elem>
void myInsert(Coll& coll, Elem&& elem) {
coll.insert(std::move(elem));
}
int main() {
auto hash = [](A const&){ return 1; };
std::unordered_set<A, decltype(hash)> s;
myInsert(s, A{});
}
尝试编译上面的代码。 对于非模板化代码的编译时错误,您很可能会得到一个较长的错误(但相信我,它很短!我实际上包含<cmath>
是为了触发更多实例化尝试以使错误更长一点)。 这是我得到的:
In file included from /usr/include/c++/12.2.0/unordered_set:44,
from example.cpp:3:
/usr/include/c++/12.2.0/bits/stl_function.h: In instantiation of ‘constexpr bool std::equal_to<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = A]’:
/usr/include/c++/12.2.0/bits/hashtable_policy.h:1701:18: required from ‘bool std::__detail::_Hashtable_base<_Key, _Value, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _Traits>::_M_key_equals_tr(const _Kt&, const std::__detail::_Hash_node_value<_Value, typename _Traits::__hash_cached::value>&) const [with _Kt = A; _Key = A; _Value = A; _ExtractKey = std::__detail::_Identity; _Equal = std::equal_to<A>; _Hash = main()::<lambda(const A&)>; _RangeHash = std::__detail::_Mod_range_hashing; _Unused = std::__detail::_Default_ranged_hash; _Traits = std::__detail::_Hashtable_traits<true, true, true>; typename _Traits::__hash_cached = std::__detail::_Hashtable_traits<true, true, true>::__hash_cached]’
/usr/include/c++/12.2.0/bits/hashtable.h:2237:32: required from ‘std::pair<typename std::__detail::_Insert<_Key, _Value, _Alloc, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::iterator, bool> std::_Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::_M_insert_unique(_Kt&&, _Arg&&, const _NodeGenerator&) [with _Kt = A; _Arg = A; _NodeGenerator = std::__detail::_AllocNode<std::allocator<std::__detail::_Hash_node<A, true> > >; _Key = A; _Value = A; _Alloc = std::allocator<A>; _ExtractKey = std::__detail::_Identity; _Equal = std::equal_to<A>; _Hash = main()::<lambda(const A&)>; _RangeHash = std::__detail::_Mod_range_hashing; _Unused = std::__detail::_Default_ranged_hash; _RehashPolicy = std::__detail::_Prime_rehash_policy; _Traits = std::__detail::_Hashtable_traits<true, true, true>; typename std::__detail::_Insert<_Key, _Value, _Alloc, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::iterator = std::__detail::_Insert_base<A, A, std::allocator<A>, std::__detail::_Identity, std::equal_to<A>, main()::<lambda(const A&)>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, true, true> >::iterator; typename _Traits::__constant_iterators = std::__detail::_Hashtable_traits<true, true, true>::__constant_iterators]’
/usr/include/c++/12.2.0/bits/hashtable.h:906:27: required from ‘std::pair<typename std::__detail::_Insert<_Key, _Value, _Alloc, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::iterator, bool> std::_Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::_M_insert(_Arg&&, const _NodeGenerator&, std::true_type) [with _Arg = A; _NodeGenerator = std::__detail::_AllocNode<std::allocator<std::__detail::_Hash_node<A, true> > >; _Key = A; _Value = A; _Alloc = std::allocator<A>; _ExtractKey = std::__detail::_Identity; _Equal = std::equal_to<A>; _Hash = main()::<lambda(const A&)>; _RangeHash = std::__detail::_Mod_range_hashing; _Unused = std::__detail::_Default_ranged_hash; _RehashPolicy = std::__detail::_Prime_rehash_policy; _Traits = std::__detail::_Hashtable_traits<true, true, true>; typename std::__detail::_Insert<_Key, _Value, _Alloc, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::iterator = std::__detail::_Insert_base<A, A, std::allocator<A>, std::__detail::_Identity, std::equal_to<A>, main()::<lambda(const A&)>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, true, true> >::iterator; typename _Traits::__constant_iterators = std::__detail::_Hashtable_traits<true, true, true>::__constant_iterators; std::true_type = std::integral_constant<bool, true>]’
/usr/include/c++/12.2.0/bits/hashtable_policy.h:1035:22: required from ‘std::__detail::_Insert<_Key, _Value, _Alloc, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits, true>::__ireturn_type std::__detail::_Insert<_Key, _Value, _Alloc, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits, true>::insert(value_type&&) [with _Key = A; _Value = A; _Alloc = std::allocator<A>; _ExtractKey = std::__detail::_Identity; _Equal = std::equal_to<A>; _Hash = main()::<lambda(const A&)>; _RangeHash = std::__detail::_Mod_range_hashing; _Unused = std::__detail::_Default_ranged_hash; _RehashPolicy = std::__detail::_Prime_rehash_policy; _Traits = std::__detail::_Hashtable_traits<true, true, true>; __ireturn_type = std::__detail::_Insert<A, A, std::allocator<A>, std::__detail::_Identity, std::equal_to<A>, main()::<lambda(const A&)>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, true, true>, true>::__ireturn_type; value_type = A]’
/usr/include/c++/12.2.0/bits/unordered_set.h:426:27: required from ‘std::pair<typename std::_Hashtable<_Value, _Value, _Alloc, std::__detail::_Identity, _Pred, _Hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<std::__not_<std::__and_<std::__is_fast_hash<_Hash>, std::__is_nothrow_invocable<const _Hash&, const _Tp&> > >::value, true, true> >::iterator, bool> std::unordered_set<_Value, _Hash, _Pred, _Alloc>::insert(value_type&&) [with _Value = A; _Hash = main()::<lambda(const A&)>; _Pred = std::equal_to<A>; _Alloc = std::allocator<A>; typename std::_Hashtable<_Value, _Value, _Alloc, std::__detail::_Identity, _Pred, _Hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<std::__not_<std::__and_<std::__is_fast_hash<_Hash>, std::__is_nothrow_invocable<const _Hash&, const _Tp&> > >::value, true, true> >::iterator = std::__detail::_Insert_base<A, A, std::allocator<A>, std::__detail::_Identity, std::equal_to<A>, main()::<lambda(const A&)>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, true, true> >::iterator; value_type = A]’
example.cpp:28:16: required from ‘void myInsert(Coll&, Elem&&) [with Coll = std::unordered_set<A, main()::<lambda(const A&)> >; Elem = A]’
example.cpp:34:13: required from here
/usr/include/c++/12.2.0/bits/stl_function.h:378:20: error: no match for ‘operator==’ (operand types are ‘const A’ and ‘const A’)
378 | { return __x == __y; }
| ~~~~^~~~~~
In file included from /usr/include/c++/12.2.0/bits/stl_algobase.h:67,
from /usr/include/c++/12.2.0/bits/specfun.h:45,
from /usr/include/c++/12.2.0/cmath:1935,
from example.cpp:1:
/usr/include/c++/12.2.0/bits/stl_iterator.h:530:5: note: candidate: ‘template<class _IteratorL, class _IteratorR> constexpr bool std::operator==(const reverse_iterator<_IteratorL>&, const reverse_iterator<_IteratorR>&) requires requires{{std::operator==::__x->base() == std::operator==::__y->base()} -> decltype(auto) [requires std::convertible_to<<placeholder>, bool>];}’ (reversed)
530 | operator==(const reverse_iterator<_IteratorL>& __x,
| ^~~~~~~~
/usr/include/c++/12.2.0/bits/stl_iterator.h:530:5: note: template argument deduction/substitution failed:
/usr/include/c++/12.2.0/bits/stl_function.h:378:20: note: ‘const A’ is not derived from ‘const std::reverse_iterator<_IteratorL>’
378 | { return __x == __y; }
| ~~~~^~~~~~
/usr/include/c++/12.2.0/bits/stl_iterator.h:1656:5: note: candidate: ‘template<class _IteratorL, class _IteratorR> constexpr bool std::operator==(const move_iterator<_IteratorL>&, const move_iterator<_IteratorR>&) requires requires{{std::operator==::__x->base() == std::operator==::__y->base()} -> decltype(auto) [requires std::convertible_to<<placeholder>, bool>];}’ (reversed)
1656 | operator==(const move_iterator<_IteratorL>& __x,
| ^~~~~~~~
/usr/include/c++/12.2.0/bits/stl_iterator.h:1656:5: note: template argument deduction/substitution failed:
/usr/include/c++/12.2.0/bits/stl_function.h:378:20: note: ‘const A’ is not derived from ‘const std::move_iterator<_IteratorL>’
378 | { return __x == __y; }
| ~~~~^~~~~~
In file included from /usr/include/c++/12.2.0/unordered_set:40:
/usr/include/c++/12.2.0/bits/allocator.h:219:5: note: candidate: ‘template<class _T1, class _T2> constexpr bool std::operator==(const allocator<_Up>&, const allocator<_T2>&)’ (reversed)
219 | operator==(const allocator<_T1>&, const allocator<_T2>&)
| ^~~~~~~~
/usr/include/c++/12.2.0/bits/allocator.h:219:5: note: template argument deduction/substitution failed:
/usr/include/c++/12.2.0/bits/stl_function.h:378:20: note: ‘const A’ is not derived from ‘const std::allocator<_Up>’
378 | { return __x == __y; }
| ~~~~^~~~~~
In file included from /usr/include/c++/12.2.0/bits/stl_algobase.h:64:
/usr/include/c++/12.2.0/bits/stl_pair.h:640:5: note: candidate: ‘template<class _T1, class _T2> constexpr bool std::operator==(const pair<_T1, _T2>&, const pair<_T1, _T2>&)’
640 | operator==(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
| ^~~~~~~~
/usr/include/c++/12.2.0/bits/stl_pair.h:640:5: note: template argument deduction/substitution failed:
/usr/include/c++/12.2.0/bits/stl_function.h:378:20: note: ‘const A’ is not derived from ‘const std::pair<_T1, _T2>’
378 | { return __x == __y; }
| ~~~~^~~~~~
/usr/include/c++/12.2.0/bits/stl_iterator.h:589:5: note: candidate: ‘template<class _Iterator> constexpr bool std::operator==(const reverse_iterator<_IteratorL>&, const reverse_iterator<_IteratorL>&) requires requires{{std::operator==::__x->base() == std::operator==::__y->base()} -> decltype(auto) [requires std::convertible_to<<placeholder>, bool>];}’
589 | operator==(const reverse_iterator<_Iterator>& __x,
| ^~~~~~~~
/usr/include/c++/12.2.0/bits/stl_iterator.h:589:5: note: template argument deduction/substitution failed:
/usr/include/c++/12.2.0/bits/stl_function.h:378:20: note: ‘const A’ is not derived from ‘const std::reverse_iterator<_IteratorL>’
378 | { return __x == __y; }
| ~~~~^~~~~~
/usr/include/c++/12.2.0/bits/stl_iterator.h:1726:5: note: candidate: ‘template<class _Iterator> constexpr bool std::operator==(const move_iterator<_IteratorL>&, const move_iterator<_IteratorL>&)’
1726 | operator==(const move_iterator<_Iterator>& __x,
| ^~~~~~~~
/usr/include/c++/12.2.0/bits/stl_iterator.h:1726:5: note: template argument deduction/substitution failed:
/usr/include/c++/12.2.0/bits/stl_function.h:378:20: note: ‘const A’ is not derived from ‘const std::move_iterator<_IteratorL>’
378 | { return __x == __y; }
| ~~~~^~~~~~
我挑战你阅读它并告诉我它很容易理解告诉我们的内容。
有什么选择? 使用std::enable_if
强制提供给myInsert
的类型必须满足的条件。 一个简化且不完整的尝试包括更改线路
template<typename Coll, typename Elem>
至
template<typename Coll, typename Elem, std::enable_if_t<equatable_v<Elem>> = 0>
这导致错误变得更短:
example.cpp: In function ‘int main()’:
example.cpp:34:13: error: no matching function for call to ‘myInsert(std::unordered_set<A, main()::<lambda(const A&)> >&, A)’
34 | myInsert(s, A{});
| ~~~~~~~~^~~~~~~~
example.cpp:27:6: note: candidate: ‘template<class Coll, class Elem, typename std::enable_if<equatable_v<Elem>, int>::type <anonymous> > void myInsert(Coll&, Elem&&)’
27 | void myInsert(Coll& coll, Elem&& elem) {
| ^~~~~~~~
example.cpp:27:6: note: template argument deduction/substitution failed:
example.cpp:23:46: error: no type named ‘type’ in ‘struct std::enable_if<false, int>’
23 | , std::enable_if_t<equatable_v<Elem>, int> = 0>
| ^
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.