代码之家  ›  专栏  ›  技术社区  ›  Chris Kline

表达式SFINAE:如何根据类型是否包含具有一个或多个参数的函数来选择模板版本

  •  3
  • Chris Kline  · 技术社区  · 7 年前

    我试图在编译时根据参数是否实现特定函数在不同的模板实现之间进行选择。这是一个常见的问题(参见 this S.O. question this example 引用人 this article

    大多数示例都显示了如何使用表达式SFINAE根据零参数函数的存在进行选择。我试图通过使用 declval (松散地基于 this example ),但我似乎无法让它工作。

    bool Util::Container::Contains(container, value) 它将使用容器的内置 find(value) 方法,否则使用 std::find(...)

    请注意: 我知道我可以通过重载unordered\u map、unordered\u set等的Contains()来实现这一点,但是我想找出这种基于模式的方法,这样它就可以自动委托给任何容器的 查找(值)

    #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,我正试图在VisualStudio2017 v15.8中实现这一点

    3 回复  |  直到 7 年前
        1
  •  2
  •   Max Langhof    7 年前

    眼前的问题是,你要传递给他的论点 Test YesType 版本。

    例如, Detail::HasFindMethod<std::unordered_set<int>> 将导致以下两个 试验 签名(因为 find iterator

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

    你试着打电话 试验 0 ,不能转换为 迭代器

    作为解决方案,请使用指针:

            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) };
    

    现在我们会有歧义(the Test(...) 也会匹配),所以我们可以让它更匹配:

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

    enable_if 确实有效)。只是解释一下你在这里尝试的那个特殊的塞子。

        2
  •  4
  •   Jarod42    7 年前

    一个简单的方法 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&);
    

    必须使用优先级,如上所示,或创建一组独占的重载:

    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函数执行此操作

    C 有一个方法 find (无过载)。

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

    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 .

        3
  •  1
  •   r3mus n0x    7 年前

    在我看来,使用 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;
    

    您可以进一步了解此实用程序及其在中引入的模式 this paper .