Callableコンセプトを使用すれば、result_typeを持っていない関数オブジェクトや関数ポインタでも、関数(オブジェクト)の戻り値の型を取得することができます。
auto concept Callable<typename F, typename... Args> { typename result_type; result_type operator()(F&&, Args...); }
#include <iostream> #include <typeinfo> #include <concepts> struct functor { bool operator()(int) const { return true; } }; int func(int) { return 0; } template <class F> requires std::Callable<F, int> void foo(F f) { typedef F::result_type result; // Fの戻り値の型を取得 std::cout << typeid(result).name() << std::endl; } int main() { foo(functor()); // bool foo(func); // int }
class Fとrequiresを分けるのがめんどくさい場合、以下のように書くこともできます
template <std::Callable<auto, int> F> void foo(F f)
これはどちらも同じ意味になります。
autoはプレースホルダーです。
また、Callableを継承したPredicateコンセプトというのがあります。
auto concept Predicate<typename F, typename... Args> : Callable<F, const Args&...> { requires Convertible<result_type, bool>; }
これは、戻り値がboolに変換可能な型でなければならないCallableコンセプトです。
その名の通り、述語として使用します。
#include <concepts> #include <iterator_concepts> struct is_even { bool operator()(int x) const { return x % 2 == 0; } }; template <std::InputIterator Iter, class Pred> requires std::Predicate<Pred, Iter::value_type> Iter find_if(Iter first, Iter last, Pred pred) { while (first != last) { if (pred(*first)) break; ++first; } return first; } int main() { int ar[] = {1, 2, 3}; find_if(ar, ar+3, is_even()); }
class Predとrequiresを分けるのがめんどくさいなら以下のようにも書けます
template <std::InputIterator Iter, std::Predicate<auto, Iter::value_type> Pred> Iter find_if(Iter first, Iter last, Pred pred)
こういうのを使えば、Boostのlambda_functorの戻り値の型とかも取れますね。