Vincent
Vincent

Reputation: 60381

Create an overload set class relying on std::invoke with classical overload resolution rules

I would like to design a class:

template <class... F> class overload_set;

that will take a list of callables at construction, and will have an operator() that will apply the classical overload resolutions rules to decide which function to call.

For two functions, this would be something like this:

template <class F0, class F1>
struct overload_set: F0, F1
{
    constexpr overload_set(F0 f0, F1 f1): F0(f0), F1(f1) {}
    using F0::operator();
    using F1::operator();
};

However, this would only work with function objects, while I would like it to work with any callable working with std::invoke (free functions, functors, lambdas, functions members, members...). And I would like it of course, to work with all the subtelties of the ref/const qualification of function members like in:

struct subtle
{
    constexpr void operator()() noexcept {}
    constexpr void operator()() & noexcept {}
    constexpr void operator()() && noexcept {}
    constexpr void operator()() const& noexcept {}
    constexpr void operator()() const&& noexcept {}
};

If subtle is passed to overload_set it should work well.

Is it possible to create such a class overload_set in C++17, and if so how (with as much template metaprogramming tricks as required, but as little C macros as possible (hopefully none))?

Upvotes: 0

Views: 168

Answers (1)

Passer By
Passer By

Reputation: 21160

Implementing what @KerrekSB said

template<typename F>
struct functor
{
    using type = F;
};

template<bool Noexcept, typename R, typename... Args>
struct functor<R (*)(Args...) noexcept(Noexcept)>
{
    struct fn
    {
        R (*p)(Args...) noexcept(Noexcept);
        R operator()(Args&&... args) const noexcept(Noexcept)
        {
            return p(std::forward<Args>(args)...);   
        }
    };
    using type = fn;
};

template<typename F>
using func = typename functor<std::decay_t<F>>::type;

template<typename... Fs>
struct over : func<Fs>...
{
    template<typename... Gs>
    over(Gs&&... gs) : func<Fs>{std::forward<Gs>(gs)}... {}

    using func<Fs>::operator()...;
};

template<typename... Gs>
auto makeover(Gs&&... gs)
{
    return over<func<Gs>...>{std::forward<Gs>(gs)...};   
}

You use it as

int main()
{
    auto o = makeover([](int){ std::cout << "int\n"; }, 
                     +[](double){ std::cout << "double\n"; });
    o(42);    // prints int
    o(42.0);  // prints double

    auto o2 = makeover(+[]() noexcept {});
    std::cout << noexcept(o2());  // prints 1
}

Demo

Upvotes: 1

Related Questions