我要检查容器内是否存在元素。
我想写一个 generic 函数来利用容器的结构。
特别是,该函数应该为支持该数据结构的数据结构选择方法count()
(例如std::set
,std::unordered_set
,...)。
在C ++ 17中,我们可以编写如下内容:
#include <algorithm>
#include <iterator>
template <typename Container, typename Element>
constexpr bool hasElement(const Container& c, const Element& e) {
if constexpr (hasCount<Container>::value) {
return c.count(e);
} else {
return std::find(std::cbegin(c), std::cend(c), e) != std::cend(c);
}
}
好的!现在我们需要实现hasCount<T>
trait 。
使用 SFINAE (以及C ++ 17中的std::void_t
),我们可以编写如下内容:
#include <type_traits>
template <typename T, typename = std::void_t<>>
struct hasCount: std::false_type {};
template <typename T>
struct hasCount<T, std::void_t<decltype(&T::count)>> : std::true_type {};
这种方法效果很好。例如,以下代码段(当然具有以前的定义)进行编译:
struct Foo {
int count();
};
struct Bar {};
static_assert(hasCount<Foo>::value);
static_assert(!hasCount<Bar>::value);
当然,我将在{em> STL 数据结构上使用hasCount
,例如std::vector
和std::set
。这里有问题!
自C ++ 14起,std::set<T>::count
具有模板重载。
因此static_assert(hasCount<std::set<int>>::value);
失败!
这是因为decltype(&std::set<int>::count)
由于过载而无法自动推论。
给出上下文:
hasCount
特征? ( C ++ 20概念不可用)。应避免外部依赖项(库,例如 boost )。
答案 0 :(得分:2)
有没有办法解决自动超载?
是的,如果您知道传递给方法的参数类型。就您而言,如果我理解正确,请Element
。
您的答案显示了如何解决修改原始代码的问题。接下来,我提出一个基于仅声明的constexpr
函数的解决方案
还有另一种写更好的hasCount特征的方法吗?
我不知道是否更好,但是通常我更喜欢使用constexpr
函数。
以下内容(警告:未经测试的代码 tested直接来自OP)
template <typename...>
constexpr std::false_type hasCountF (...);
template <typename T, typename ... As>
constexpr auto hasCountF (int)
-> decltype( std::declval<T>().count(std::declval<As>()...), std::true_type{});
template <typename ... Ts>
using has_count = decltype(hasCountF<Ts...>(1));
,也许也(仅限于C ++ 14)
template <typename ... Ts>
constexpr auto has_count_v = has_count<Ts...>::value:
您可以按以下方式调用它
if constexpr ( has_count_v<Container, Element> )
在您的情况下,在函数中使用Container
c
和Element
e
,可以使其更简单(避免使用许多std::declval()
' s),您可以尝试以下几种功能
template <typename...>
constexpr std::false_type hasCountF (...);
template <typename C, typename ... As>
constexpr auto hasCountF (C const & c, As const & ... as)
-> decltype( c.count(as...), std::true_type{});
调用如下
if constexpr ( decltype(hasCountF(c, e))::value )
但是我更喜欢前面的解决方案,因为它需要更多的打字,但是更加灵活。
答案 1 :(得分:1)
从问题注释中,正确的方法是检查“ 调用表达式”(而不是方法的存在)。
因此, trait结构的改进可能如下:
#include <type_traits>
template <typename T, typename U, typename = std::void_t<>>
struct hasCount : std::false_type {};
template <typename T, typename U>
struct hasCount<T, U, std::void_t<decltype(std::declval<T>().count(std::declval<U>()))>> :
std::true_type {};
给出两个分别为t
和u
类型的实例T
和U
,它检查表达式t.count(u)
是否有效。
因此以下代码有效:
static_assert(hasCount<std::set<int>, int>::value);
解决问题中的问题。
通用算法现在可以通过以下方式实现:
#include <algorithm>
#include <iterator>
template <typename Container, typename Element>
constexpr bool hasElement(const Container& c, const Element& e) {
if constexpr (hasCount<Container, Element>::value) {
return c.count(e);
} else {
return std::find(std::cbegin(c), std::cend(c), e) != std::cend(c);
}
}
答案 2 :(得分:0)
在这样的地方简单地遵循具有后备功能的单独的过载集很有帮助:
template <typename Container, typename Element>
constexpr auto hasElement_impl(int, const Container& c, const Element& e)
-> decltype(c.count(e))
{
return c.count(e);
}
template <typename Container, typename Element>
constexpr bool hasElement_impl(long, const Container& c, const Element& e)
{
return std::find(c.begin(), c.end(), e) != c.end();
}
template <typename Container, typename Element>
constexpr bool hasElement(const Container& c, const Element& e)
{
return hasElement_impl(0, c, e);
}
如果您可以执行c.count(e)
,请执行此操作。否则,回退到find()
。在这种情况下,您无需编写类型特征,而实际上问题本身就说明了尝试采用该特征的问题。更简单了。
或者,使用Boost.HOF之类的东西:
constexpr inline auto hasElement = boost::hof::first_of(
[](auto const& cnt, auto const& elem) BOOST_HOF_RETURNS(cnt.count(elem)),
[](auto const& cnt, auto const& elem) {
return std::find(cnt.begin(), cnt.end(), elem) != cnt.end();
}
);
在C ++ 20中,由于概念的原因,这种算法的改进将变得容易得多
template <AssociativeContainer C, typename E>
bool hasElement(const C& c, const E& e) { return c.count(e); }
template <typename C, typename E>
bool hasElement(const C& c, const E& e) { return std::find(c.begin(), c.end(), e) != c.end(); }