簡體   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