我正在尝试编写一个通用的erase_if
方法,可以在任何容器类型上使用它来删除给定谓词的元素。如果容器允许,它应该使用erase-remove-idiom,或者循环容器并调用erase
方法。我还想提供容器本身,而不是分别提供begin
和end
迭代器。这应由方法处理。
但是,我无法通过SFINAE来获取元模板以区分这两种情况。我正在尝试检查方法std::remove_if
(或std::remove
)是否已针对给定类型进行了明确定义,但true
和{vector
的值均为map
{1}}(在这种情况下代码不会编译)或false
两者。我对模板元编程很陌生,所以有什么我想念的吗?或许还有另一个更好的解决方案?
以下是我的示例代码:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
#include <type_traits>
#include <vector>
namespace my_std
{
using std::begin;
using std::end;
namespace detail
{
template <typename T>
// this is false for both vector and map
auto is_remove_compatible(int) -> decltype(std::remove_if(begin(std::declval<T&>()), end(std::declval<T&>()),
[](auto&&) { return false; }),
std::true_type{});
// this is true for both vector and map
// auto is_remove_compatible(int)
// -> decltype(std::remove(begin(std::declval<T&>()), end(std::declval<T&>()), std::declval<T::value_type&>()),
// std::true_type{});
template <typename T>
auto is_remove_compatible(...) -> std::false_type;
}
template <typename T>
using is_remove_compatible = decltype(detail::is_remove_compatible<T>(0));
template <typename T>
constexpr bool is_remove_compatible_v = is_remove_compatible<T>::value;
template <typename Cont, typename Pred>
std::enable_if_t<is_remove_compatible_v<Cont>> erase_if(Cont& container, Pred pred)
{
std::cout << "Using erase-remove\n";
container.erase(std::remove_if(begin(container), end(container), pred), end(container));
}
template <typename Cont, typename Pred>
std::enable_if_t<!is_remove_compatible_v<Cont>> erase_if(Cont& container, Pred pred)
{
std::cout << "Using loop\n";
for (auto it = begin(container); it != end(container);)
{
if (pred(*it))
it = container.erase(it);
else
++it;
}
}
}
template <typename T>
std::ostream& operator<<(std::ostream& out, std::vector<T> const& v)
{
if (!v.empty())
{
out << '[';
std::copy(v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
out << "\b\b]";
}
return out;
}
template <typename K, typename V>
std::ostream& operator<<(std::ostream& out, std::map<K, V> const& v)
{
out << '[';
for (auto const& p : v)
out << '{' << p.first << ", " << p.second << "}, ";
out << "\b\b]";
return out;
}
int main(int argc, int argv[])
{
auto vp = my_std::is_remove_compatible_v<std::vector<int>>;
auto mp = my_std::is_remove_compatible_v<std::map<int, int>>;
std::cout << vp << ' ' << mp << '\n';
std::vector<int> v = {1, 2, 3, 4, 5};
auto v2 = v;
my_std::erase_if(v2, [](auto&& x) { return x % 2 == 0; });
std::map<int, int> m{{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}};
auto m2 = m;
my_std::erase_if(m2, [](auto&& x) { return x.first % 2 == 0; });
std::cout << v << " || " << v2 << '\n';
std::cout << m << " || " << m2 << '\n';
std::cin.ignore();
}
答案 0 :(得分:7)
这个想法有两个原因:
auto is_remove_compatible(int) -> decltype(std::remove_if(begin(std::declval<T&>()), end(std::declval<T&>()),
[](auto&&) { return false; }),
std::true_type{});
首先,您无法在未评估的上下文中拥有lambda,因此代码格式不正确。其次,即使它没有形成(很容易解决),remove_if()
对任何事情都不具备SFINAE友好性。如果你将一个不是谓词的东西传递给remove_if
,或者传递一个不可修改的迭代器,那么标准不要求它只是一个硬错误。
我现在这样做的方式是通过选择者的习语。基本上,有基于排名顺序的条件sfinae:
template <std::size_t I> struct chooser : chooser<I-1> { };
template <> struct chooser<0> { };
template <class Cont, class Predicate>
void erase_if(Cont& container, Predicate&& predicate) {
return erase_if_impl(container, std::forward<Predicate>(predicate), chooser<10>{});
}
然后我们按顺序拥有各种erase_if_impl
条件:
// for std::list/std::forward_list
template <class Cont, class Predicate>
auto erase_if_impl(Cont& c, Predicate&& predicate, chooser<3> )
-> decltype(void(c.remove_if(std::forward<Predicate>(predicate))))
{
c.remove_if(std::forward<Predicate>(predicate));
}
// for the associative containers (set, map, ... )
template <class Cont, class Predicate>
auto erase_if_impl(Cont& c, Predicate&& predicate, chooser<2> )
-> decltype(void(c.find(std::declval<typename Cont::key_type const&>())))
{
using std::begin; using std::end;
for (auto it = begin(c); it != end(c); )
{
if (predicate(*it)) {
it = c.erase(it);
} else {
++it;
}
}
}
// for everything else, there's MasterCard
template <class Cont, class Predicate>
void erase_if_impl(Cont& c, Predicate&& predicate, chooser<1> )
{
using std::begin; using std::end;
c.erase(std::remove_if(begin(c), end(c), std::forward<Predicate>(predicate)), end(c));
}