Я хочу проверить, присутствует ли элемент внутри контейнера или нет.
Я хотел бы написать функцию общий, которая использует структуру контейнера.
В частности, функция должна выбрать метод 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>черта характера.
С СФИНАЕ (и std::void_t в C++ 17) мы можем написать что-то вроде:
#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);
Конечно, я собираюсь использовать hasCount в структуре данных STL, такой как 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 недоступны).Следует избегать внешних зависимостей (библиотек, таких как увеличение).
в любом случае вы должны потребовать, чтобы у контейнера был метод count, который можно вызвать, а не только то, что у него есть какой-то член с именем count, если вы это сделаете, я думаю, что проблема исчезнет
@BoBTFish хорошая идея!





Из комментария к вопросу, правильный подход - проверить "выражение вызова" (а не существование метода).
Следовательно, улучшение структура черты может быть следующим:
#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);
}
}
Имейте в виду, что было бы более эффективно проверять член find, а не член count, так как count будет выполнять больше работы в контейнере multi_.
@BoBTFish, вы так говорите, потому что count должен "считать" ключи все? То есть: в случае контейнера mult_ вы не можете ограничить «найти первый», но вы должны пересчитать их все.
hasCount<const Container&, const Element&>::value - это на самом деле то, что вы хотите утверждать
@BiagioFesta Именно так. Также стоит отметить, что для C++ 20 предлагается добавить функцию-член contains в ассоциативные контейнеры. Конечно, сейчас от вас мало толку, но стоит знать.
@BoBTFish да, спасибо. Я уже знал. Однако асимптотически мы все еще можем полагаться на count. Худший случай подразумевает контейнер multi_ (который я никогда в жизни не видел в производственном коде) и случай, когда у вас много повторяющихся ключей.
is there a way to solve the automatic overload?
Да, если вы знаете типы аргументов, передаваемых методу. В вашем случае, если я правильно понял, Element.
Ваш ответ покажет, как решить проблему, изменив исходный код. Далее я предлагаю решение на основе только заявленных функций constexpr.
is there another way to write a better hasCount trait?
Не знаю, лучше ли, но обычно я предпочитаю использовать функции constexpr.
Что-то вроде (Внимание: код не протестированпроверено прямо из ОП)
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> )
В вашем случае, используя Containerc и Elemente в вашей функции, вы можете упростить ее (избегая большого количества std::declval()), и вы можете попробовать с парой функций следующим образом
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 )
но я предпочитаю предыдущее решение, потому что требуется больше машинописного ввода, но оно более гибкое.
Прохладный! Я тестировал здесь. Если хотите, можете добавить код к своему ответу.
@BiagioFesta - ответ улучшился ... ну ... не уверен, что улучшилось.
В таких местах полезно просто перейти к отдельному набору перегрузки, у которого есть резерв:
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(); }
decltypeвыражения, которое вызывает функцию со значением? то естьdecltype(declval<T>().count(declval<int>()))