簡體   English   中英

表達式SFINAE:如何根據類型是否包含具有一個或多個參數的函數來選擇模板版本

[英]Expression SFINAE: how to select template version based on whether type contains a function with one or more arguments

我試圖在編譯時選擇不同的模板實現,具體取決於參數是否實現了特定的功能。 這是一個常見的問題(參見這太問題這個例子中通過引用這篇文章 。常見的答案是“用表情SFINAE”。

大多數示例顯示了如何使用表達式SFINAE來基於零參數函數的存在進行選擇。 我試圖通過使用declval (松散地基於這個例子 )將這些用於我的單參數用例,但我似乎無法讓它工作。

我確定我在下面的例子中做錯了什么,但我無法弄清楚它是什么。 示例是嘗試定義模板bool Util::Container::Contains(container, value)兩個版本,如果存在,將使用容器的內置find(value)方法,否則將回退到線性搜索std::find(...)

請注意:我知道我可以通過為isordered_map,unordered_set等重載Contains()來完成這項工作,但我想找出這種基於模式的方法,以便它會自動委托給任何容器的find(value)無需添加過載。

#include <unordered_set>
#include <unordered_map>
#include <vector>
#include <string>

namespace Util::Container {

    namespace Detail
    {
        template <typename T>
        class HasFindMethod
        {
        private:
            typedef char YesType[1];
            typedef char NoType[2];

            // This is how the examples show it being done for a 0-arg function
            //template <typename C> static YesType& Test(decltype(&C::find));

            // Here's my attempt to make it match a 1-arg function
            template <typename C> static YesType& 
                Test(decltype(std::declval<C>().find(std::declval<const C::value_type&>())));

            template <typename C> static NoType& Test(...);

        public:
            enum { value = sizeof(Test<T>(0)) == sizeof(YesType) };
        };
    }

    // Fallback: uses std::find() to do the lookup if no type-specific T::find(value) exists
    template<typename T>
    bool Contains(const T& in_container, const typename T::value_type& in_item)
    {
        const auto& result = std::find(in_container.cbegin(), in_container.cend(), in_item);
        return (result != in_container.cend());
    }

    // Preferred: use T::find() to do the lookup if possible
    template<typename T>
    inline typename std::enable_if<Detail::HasFindMethod<T>::value, bool>::type 
        Contains(const T& in_container, const typename T::value_type& in_item)
    {
        return (in_container.find(in_item) != in_container.end());
    }
}

int main()
{
    const std::vector<int> v { 1, 2, 3 };
    const std::unordered_map<int, std::string> m { {1,"1" }, {2,"2"} };
    const std::unordered_set<std::string> s { "1" , "2" };

    // These should use the std::find()-based version of Contains() since vector and unordered_map
    // have no find(value_type) method. And they do.
    const bool r_v = Util::Container::Contains(v, 2);
    const bool r_m = Util::Container::Contains(m, { 2, "2" });

    // !!!!!! 
    // 
    // This should use the T::find(value_type)-based version of Contains() since
    // unordered_set has a find(value_type) method.
    //
    // But it doesn't --- that's the issue I'm trying to solve.
    // 
    const bool r_s = Util::Container::Contains(s, "2");
}

如果有人能告訴我如何解決這個問題,我將非常感激。

FWIW,我試圖在Visual Studio 2017 v15.8中實現它

decltype一個簡單方法是

template<typename C, typename V>
auto Contains(const C& c, const V& value)
-> decltype(std::find(c.cbegin(), c.cend(), value) != c.cend())
{
    return std::find(c.cbegin(), c.cend(), value) != c.cend();
}

template <typename C, typename Key>
auto Contains(const C& c, const Key& key)
-> decltype(c.find(key) != c.end())
{
    return c.find(key) != c.end();
}

但是當兩個功能都可能時,你會有一個模糊的電話。

所以只需添加額外的參數來確定重載的優先級:

struct low_priority {};
struct high_priority : low_priority {};


template<typename C, typename V>
auto ContainsImpl(low_priority, const C& c, const V& value)
-> decltype(std::find(c.cbegin(), c.cend(), value) != c.cend())
{
    return std::find(c.cbegin(), c.cend(), value) != c.cend();
}

template <typename C, typename Key>
auto ContainsImpl(high_priority, const C& c, const Key& key)
-> decltype(c.find(key) != c.end())
{
    return c.find(key) != c.end();
}

template <typename C, typename T>
auto Contains(const C& c, const T& t)
-> decltype(ContainsImpl(high_priority{}, c, t))
{
    return ContainsImpl(high_priority{}, c, t);
}

現在關於你的版本,你有幾個問題

最后一個:

// Expected Fallback: uses std::find() to do the lookup if no type-specific T::find(value) exists
template<typename T>
bool Contains(const T&, const typename T::value_type&);

// Expected Preferred: use T::find() to do the lookup if possible
template<typename T>
typename std::enable_if<Detail::HasFindMethod<T>::value, bool>::type 
Contains(const T&, const typename T::value_type&);

SFINAE允許丟棄過載,但不能優先考慮它們。 您必須使用優先級,如上所示,或創建獨占的重載集:

template<typename T>
typename std::enable_if<!Detail::HasFindMethod<T>::value, bool>::type 
Contains(const T&, const typename T::value_type&);

template<typename T>
typename std::enable_if<Detail::HasFindMethod<T>::value, bool>::type 
Contains(const T&, const typename T::value_type&);

除此之外,如評論map所提到的,系列將使用key_type而不是value_type

然后你的檢測代碼是錯誤的,

//這是示例如何顯示為0-arg函數//模板靜態YesType&Test(decltype(&C :: find));

不,這可以檢測C是否有方法find (沒有過載)。

 template <typename C> static YesType& Test(decltype(std::declval<C>().find(std::declval<const C::value_type&>()))); 

在這里,您使用SFINAE,但最終類型將是( const_iterator ,並且Test<C>(0)將不會采用該重載(除非迭代器可以從0構建,這不是常規情況)。 添加額外*是可能的,然后你有迭代器上的指針,它可能被0初始化。

否則,您可以使用提供的鏈接中提供的代碼:

namespace detail{
  template<class T, typename ... Args>
  static auto test_find(int)
      -> sfinae_true<decltype(std::declval<T>().find(std::declval<const Arg&>()...))>;
  template<class, class ...>
  static auto test_find(long) -> std::false_type;
} // detail::

template<class C, typename ... Args>
struct has_find : decltype(detail::test_find<T, Args...>(0)){};
// int has higher priority than long for overload resolution

然后使用std::enable_if has_find<Container, Key>::value

當前的問題是您傳遞給Test的參數與YesType版本不兼容。

例如, Detail::HasFindMethod<std::unordered_set<int>>將導致以下兩個Test簽名(因為find將返回一個iterator ):

        static YesType& Test(std::unordered_set<int>::iterator);

        static NoType& Test(...);

您嘗試使用參數0調用Test ,該參數不可轉換為iterator 因此,第二個被挑選。

作為解決方案,使用指針:

        template <typename C> static YesType& 
            Test(decltype(std::declval<C>().find(std::declval<const C::value_type&>()))*);
        //                                                                             ^

然后使用nullptr參數進行檢查:

        enum { value = sizeof(Test<T>(nullptr)) == sizeof(YesType) };

現在我們會有歧義( Test(...)也會匹配),所以我們可以使那個更糟糕的匹配:

        template <typename C, class ... Args> static NoType& Test(void*, Args...);

如其他答案所示,這仍然是一個相對復雜的解決方案(並且有更多問題阻止它在您的實例中工作,例如當enable_if工作時過載之間的模糊性)。 只是在這里解釋你嘗試的特定塞子。

使用void_t實用程序可以實現更簡單(在我看來)和更易讀的解決方案:

template <typename T, typename Dummy = void>
struct has_member_find : std::false_type { };

template <typename T>
struct has_member_find<T,
    std::void_t<decltype(std::declval<T>().find(std::declval<typename T::value_type &>()))>>
    : std::true_type { };

template<typename T>
std::enable_if_t<!has_member_find<T>::value, bool>
Contains(const T& in_container, const typename T::value_type& in_item)
{
    const auto& result = std::find(in_container.cbegin(), in_container.cend(), in_item);
    return (result != in_container.cend());
}

template<typename T>
std::enable_if_t<has_member_find<T>::value, bool>
Contains(const T& in_container, const typename T::value_type& in_item)
{
    return (in_container.find(in_item) != in_container.end());
}

請注意, void_t僅在C ++ 17之后可用,但是你沒有完整的C ++ 17支持,你可以自己定義它,因為它的定義非常簡單:

template< class... >
using void_t = void;

您可以了解有關此實用程序及其在本白皮書中介紹的模式的更多信息。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM