СФИНАЕ не решаемая перегрузка - PullRequest
0 голосов
/ 09 ноября 2018

Контекст

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

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

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

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

Ответы [ 3 ]

0 голосов
/ 09 ноября 2018

есть ли способ решить проблему автоматической перегрузки?

Да, если вы знаете типы аргументов, передаваемых методу.В вашем случае, если я правильно понимаю, Element.

Ваш ответ покажет, как решить проблему, изменив исходный код.Далее я предлагаю решение, основанное на объявленных только constexpr функциях

. Есть ли другой способ написать лучшую характеристику hasCount?

Не знаю, лучше ли, но обычно я предпочитаю использовать constexpr функции.

Что-то следующее ( Внимание: код не проверен проверен непосредственно из 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()), и вы можете попробовать с помощью пары функций следующим образом

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 )

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

0 голосов
/ 10 ноября 2018

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

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(); }
0 голосов
/ 09 ноября 2018

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

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

#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);
  }
}
...