繁体   English   中英

带有std :: enable_if的模板专业化和SFINAE之间的区别?

[英]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定义它,以便使代码在编译时失败(如果您取消注释该行代码编译并运行顺利)。

    • 你可以无视傻hash function; 它只是为了使代码编译以防您取消注释friend定义。
  • 我定义了一个Equatable概念来检查是否在给定类型T的两个 arguments 上定义了operator== ,即该类型是否适合unordered_set的元素类型; 这是有一个谓词应用于类型作为enable_if的条件。

  • myInsert是问题的 function object,模板 function 不受限制。

    • 您还可以想象您已经为另一个 class 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM