我正在编写一个contains()
实用程序函数,并提出了这个问题。我的问题是:有没有更好的方法来选择正确的函数来处理呼叫?
template <class Container>
inline auto contains(Container const& c,
typename Container::key_type const& key, int) noexcept(
noexcept(c.end(), c.find(key))) ->
decltype(c.find(key), true)
{
return c.end() != c.find(key);
}
template <class Container>
inline auto contains(Container const& c,
typename Container::value_type const& key, long) noexcept(
noexcept(c.end(), ::std::find(c.begin(), c.end(), key))
)
{
auto const cend(c.cend());
return cend != ::std::find(c.cbegin(), cend, key);
}
template <class Container, typename T>
inline auto contains(Container const& c, T const& key) noexcept(
noexcept(contains(c, key, 0))
)
{
return contains(c, key, 0);
}
答案 0 :(得分:5)
namespace details {
template<template<class...>class Z, class, class...Ts>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z,std::void_t<Z<Ts...>>,Ts...>:std::true_type{};
};
template<template<class...>class Z, class...Ts>
using can_apply=typename details::can_apply<Z,void,Ts...>::type;
这需要一个模板和参数,并告诉你是否可以应用它。
template<class T, class...Args>
using dot_find_r = decltype(std::declval<T>().find(std::declval<Args>()...));
template<class T, class...Args>
constexpr can_apply<dot_find_r, T, Args...> can_dot_find{};
我们现在标记myfind
上的调度:
template<class C>
using iterator = decltype( ::std::begin(std::declval<C>()) );
namespace details {
template<class Container, class Key>
iterator<Container const&> myfind(
std::false_type can_dot_find,
Container const& c,
Key const& key
)
noexcept(
noexcept( ::std::find(::std::begin(c), ::std::end(c), key) )
)
{
return ::std::find( ::std::begin(c), ::std::end(c), key );
}
template <class Container, class Key>
iterator<Container const&> myfind(
std::true_type can_dot_find,
Container const& c,
Key const& key
) noexcept(
noexcept( c.find(key) )
)
{
return c.find(key);
}
}
template<class Container, class Key>
iterator<Container const&> myfind(
Container const& c,
Key const& k
) noexcept (
details::myfind( can_dot_find<Container const&, Key const&>, c, k )
)
{
return details::myfind( can_dot_find<Container const&, Key const&>, c, k );
}
template<class Container, class Key>
bool contains(
Container const& c,
Key const& k
) noexcept (
noexcept( ::std::end(c), myfind( c, k ) )
)
{
return myfind(c, k) != ::std::end(c);
}
作为奖励,上述版本适用于原始C样式数组。
我要做的下一个改进是自动ADL std::begin
,以使begin
扩展在非dot_find
案例中有效。
我的个人等效内容会返回相应类型的std::optional<iterator>
。这两者都提供了一个快速的“就在那里”,并且可以轻松访问迭代器,如果不是那样的话。
if (auto oit = search_for( container, key )) {
// use *oit here as the iterator to the element, guaranteed not to be `end`
}
或
if (search_for( container, key )) {
// key was there
}
但这既不在这里也不在那里。
答案 1 :(得分:5)
为了记录,你可以写:
#include "magic.h"
template <typename T, typename... Us>
using has_find = decltype(std::declval<T>().find(std::declval<Us>()...));
template <class Container, typename T>
auto contains(const Container& c, const T& key)
{
return static_if<detect<has_find, decltype(c), decltype(key)>{}>
(
[&] (auto& cont) { return cont.end() != cont.find(key); },
[&] (auto& cont) { return cont.end() != std::find(cont.begin(), cont.end(), key); }
)(c);
}
magic.h
包含:
#include <type_traits>
template <bool> struct tag {};
template <typename T, typename F>
auto static_if(tag<true>, T t, F f) { return t; }
template <typename T, typename F>
auto static_if(tag<false>, T t, F f) { return f; }
template <bool B, typename T, typename F>
auto static_if(T t, F f) { return static_if(tag<B>{}, t, f); }
template <bool B, typename T>
auto static_if(T t) { return static_if(tag<B>{}, t, [](auto&&...){}); }
template <typename...>
using void_t = void;
template <typename AlwaysVoid, template <typename...> class Operation, typename... Args>
struct detect_impl : std::false_type {};
template <template <typename...> class Operation, typename... Args>
struct detect_impl<void_t<Operation<Args...>>, Operation, Args...> : std::true_type {};
template <template <typename...> class Operation, typename... Args>
using detect = detect_impl<void, Operation, Args...>;
答案 2 :(得分:2)
因此,如果可能,请致电c.find
std::find
。但也要像std::set
中那样警惕类型歧义。
以下是解决该问题的代码(删除了详细和微观优化以支持可读性):
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <type_traits>
#include <set>
#include <cstdarg>
using namespace std;
template <typename T, typename Ret>
struct dummy {
typedef Ret type;
};
template <class Container>
auto contains(const Container &c, typename Container::key_type const &key) ->
typename dummy<decltype(c.find(key)), bool>::type {
cout << "c.find" << endl;
return c.end() != c.find(key);
}
template <class Container, typename ...T>
typename std::enable_if<sizeof...(T)==1, bool>::type contains(const Container &c, const T&... args) {
typename Container::value_type const &val = std::get<0>(std::tuple<const T&...>(args...));
cout << "std::find" << endl;
return c.cend() != find(c.cbegin(), c.cend(), val);
}
int main() {
vector<int> v = {1,2,3};
cout << contains(v,4) << contains(v,2) << endl;
map<int, int> m;
m[1] = 1;
m[2] = 2;
m[3] = 3;
cout << contains(m,4) << contains(m,2) << endl;
set<int> s;
cout << contains(s,4) << contains(s,2) << endl;
return 0;
}
我做了什么:
contains
函数依赖于c.find()
可调用。如果不是,编译器看不到该功能,也不会出现问题key_type
和value_type
相同时,我通过使用std::find
引入函数并将其第二个必需参数作为可变参数模板来解决歧义。我还强制使用大小为1的可变参数模板。如果您只是假设现有key_type
表示存在container.find
,那么您可以简化代码并删除dummy
结构:
template <class Container>
bool contains(const Container &c, typename Container::key_type const &key)
{
return c.end() != c.find(key);
}
template <class Container, typename ...T>
typename std::enable_if<sizeof...(T)==1, bool>::type contains(const Container &c, const T&... args) {
typename Container::value_type const &val = std::get<0>(std::tuple<const T&...>(args...));
return c.cend() != find(c.cbegin(), c.cend(), val);
}
如果Container::find
确实存在,则可以完全禁用第二个功能,而不必以这种方式解决歧义。 This和That回答都显示了不同的了解方式。然后使用std::enable_if<! (Does Container have the find method?) , bool>::type
作为第二个函数的返回类型将起作用。
答案 3 :(得分:1)
由于find
存在于关联容器中,因此您可以明确地将它们设置为真正的类型。
元功能:
template <class> struct has_find_impl:std::false_type{};
template <class T, class... Args> struct has_find_impl<std::set<T, Args...>>:std::true_type{};
template <class T, class... Args> struct has_find_impl<std::map<T, Args...>>:std::true_type{};
template <class T, class... Args> struct has_find_impl<std::multiset<T, Args...>>:std::true_type{};
template <class T, class... Args> struct has_find_impl<std::multimap<T, Args...>>:std::true_type{};
template <class T> using has_find = has_find_impl<typename std::decay<T>::type>;
并像这样使用它:
template <class Container>
bool contains_impl(const Container& c, const typename Container::key_type& key, std::true_type)
{
return c.find(key) != c.cend();
}
template <class Container>
bool contains_impl(const Container& c, typename Container::const_reference key, std::false_type)
{
return std::find(c.cbegin(), c.cend(), key) != c.cend();
}
template <class Container, class T>
bool contains(const Container& c, const T& key)
{
return contains_impl(c, key, has_find<Container>{});
}
或与SFINAE一起使用。这里有一个完整的例子:
#include <iostream>
#include <algorithm>
#include <utility>
#include <map>
#include <set>
#include <vector>
#include <array>
#include <type_traits>
template <class> struct has_find_impl:std::false_type{};
template <class T, class... Args> struct has_find_impl<std::set<T, Args...>>:std::true_type{};
template <class T, class... Args> struct has_find_impl<std::map<T, Args...>>:std::true_type{};
template <class T, class... Args> struct has_find_impl<std::multiset<T, Args...>>:std::true_type{};
template <class T, class... Args> struct has_find_impl<std::multimap<T, Args...>>:std::true_type{};
template <class T> using has_find = has_find_impl<typename std::decay<T>::type>;
template <class Container>
typename std::enable_if<has_find<Container>::value, bool>::type
contains_impl(const Container& c, const typename Container::key_type& key)
{
return c.find(key) != c.cend();
}
template <class Container>
typename std::enable_if<!has_find<Container>::value, bool>::type
contains_impl(const Container& c, typename Container::const_reference key)
{
return std::find(c.cbegin(), c.cend(), key) != c.cend();
}
template <class Container, class T>
bool contains(const Container& c, const T& key)
{
return contains_impl(c, key);
}
int main()
{
std::cout << std::boolalpha;
std::array<int, 3> a = {{ 1, 2, 3 }};
std::cout << contains(a, 0) << "\n";
std::cout << contains(a, 1) << "\n\n";
std::vector<int> v = { 1, 2, 3 };
std::cout << contains(v, 0) << "\n";
std::cout << contains(v, 1) << "\n\n";
std::set<int> s = { 1, 2, 3 };
std::cout << contains(s, 0) << "\n";
std::cout << contains(s, 1) << "\n\n";
std::map<int, int> m = { { 1, 1}, { 2, 2}, { 3, 3} };
std::cout << contains(m, 0) << "\n";
std::cout << contains(m, 1) << "\n\n";
}