SFINAE неразрешимая перегрузка

Контекст

Я хочу проверить, присутствует ли элемент внутри контейнера или нет.

Я хотел бы написать функцию общий, которая использует структуру контейнера.

В частности, функция должна выбрать метод 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 недоступны).

Следует избегать внешних зависимостей (библиотек, таких как увеличение).

decltype выражения, которое вызывает функцию со значением? то есть decltype(declval<T>().count(declval<int>()))
BoBTFish 09.11.2018 12:24

в любом случае вы должны потребовать, чтобы у контейнера был метод count, который можно вызвать, а не только то, что у него есть какой-то член с именем count, если вы это сделаете, я думаю, что проблема исчезнет

463035818_is_not_a_number 09.11.2018 12:25

@BoBTFish хорошая идея!

BiagioF 09.11.2018 12:28
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
3
105
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Из комментария к вопросу, правильный подход - проверить "выражение вызова" (а не существование метода).

Следовательно, улучшение структура черты может быть следующим:

#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 09.11.2018 14:26

@BoBTFish, вы так говорите, потому что count должен "считать" ключи все? То есть: в случае контейнера mult_ вы не можете ограничить «найти первый», но вы должны пересчитать их все.

BiagioF 09.11.2018 14:37
hasCount<const Container&, const Element&>::value - это на самом деле то, что вы хотите утверждать
Piotr Skotnicki 09.11.2018 15:00

@BiagioFesta Именно так. Также стоит отметить, что для C++ 20 предлагается добавить функцию-член contains в ассоциативные контейнеры. Конечно, сейчас от вас мало толку, но стоит знать.

BoBTFish 09.11.2018 16:27

@BoBTFish да, спасибо. Я уже знал. Однако асимптотически мы все еще можем полагаться на count. Худший случай подразумевает контейнер multi_ (который я никогда в жизни не видел в производственном коде) и случай, когда у вас много повторяющихся ключей.

BiagioF 09.11.2018 17:00
Ответ принят как подходящий

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 )

но я предпочитаю предыдущее решение, потому что требуется больше машинописного ввода, но оно более гибкое.

Прохладный! Я тестировал здесь. Если хотите, можете добавить код к своему ответу.

BiagioF 09.11.2018 14:46

@BiagioFesta - ответ улучшился ... ну ... не уверен, что улучшилось.

max66 09.11.2018 14:53

В таких местах полезно просто перейти к отдельному набору перегрузки, у которого есть резерв:

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

Другие вопросы по теме