CallableコンセプトとPredicateコンセプト

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の戻り値の型とかも取れますね。