std::function
используется в двоичных разделителях, но не в качестве параметра общего назначения для функторов. Как вы только что обнаружили, его конвертирующий конструктор плохо взаимодействует с разрешением перегрузки (и это не имеет ничего общего с лямбда-выражениями). Поскольку DoSomethingIf
является уже шаблоном, я не вижу проблемы с каноническим решением принятия обобщенных функторов:
template<typename TContainer, typename Predicate>
void DoSomethingIf(TContainer& c, Predicate&& predicate);
Как вы можете заметить, эта версия не может быть перегружена и просто будет принимать что-либо в качестве предиката, даже int
. Перегрузка легко решается с помощью SFINAE, как обычно:
template<
typename Container
, typename Predicate
, typename = typename std::enable_if<
is_callable<Predicate, bool(typename Container::const_reference)>::value
>::type
>
void
DoSomethingIf(Container& container, Predicate&& predicate);
template<
typename Container
, typename Predicate
, typename = typename std::enable_if<
is_callable<Predicate, bool(typename Container::const_reference, int)>::value
>::type
// dummy parameter to disambiguate this definition from the previous one
, typename = void
>
void
DoSomethingIf(Container& container, Predicate&& predicate);
Это по-прежнему вызывает досадную проблему: если кто-то передает предикат (или что-то еще), которое не удовлетворяет нашим условиям, мы получаем ошибку «не найдена соответствующая функция» (ошибка разрешения перегрузки), а не полезную ошибку. Если вы хотите решить эту проблему, вы можете добавить перегрузку 'catch-all':
template<
typename Container
, typename Predicate
, typename = typename std::enable_if<
!is_callable<Predicate, bool(typename Container::const_reference)>::value
&& !is_callable<Predicate, bool(typename Container::const_reference, int)>::value
>::type
// more dummies
, typename = void, typename = void
>
void DoSomethingIf(Container&, Predicate&&)
{ static_assert( dependent_false_type<Container>::value,
"Put useful error message here" ); }
(dependent_false_type
просто должен быть, например, тип, унаследованный от std::false_type
, мы не можем static_assert
просто false
или это будет срабатывать каждый раз , а не только когда шаблон создается, как нам хотелось бы. В качестве альтернативы вы можете повторить условие, которое у нас есть внутри std::enable_if
, которое работает как документация внутри кода, но не улучшает саму функциональность.)
Осталось только найти is_callable<Functor, Signature>
, так как на самом деле это не стандартная черта. Это относительно легко реализовать, если вы когда-либо писали тест SFINAE раньше, но это немного утомительно, так как вам приходится частично специализироваться на void
возвратах. Я не ставлю здесь специализацию, так как этот ответ достаточно длинный.
Если вы находите это решение мощным, но слишком многословным, возможно, вам понравятся концепции:)