Описание проблемы:

C++17 вводит std::invocable<F, Args...>, что удобно для определения того, может ли тип ... вызывать с заданными аргументами. Однако есть ли способ сделать это для любых аргументов для функторов (поскольку комбинации существующих черт стандартной библиотеки уже позволяют обнаруживать функции, указатели на функции, ссылки на функции, функции-члены ...)?

Другими словами, как реализовать следующую черту типа?

template <class F>
struct is_functor {
    static constexpr bool value = /*using F::operator() in derived class works*/;
};

Пример использования:

#include <iostream>
#include <type_traits>

struct class0 {
    void f();
    void g();
};

struct class1 {
    void f();
    void g();
    void operator()(int);
};

struct class2 {
    void operator()(int);
    void operator()(double);
    void operator()(double, double) const noexcept;
};

struct class3 {
    template <class... Args> constexpr int operator()(Args&&...);
    template <class... Args> constexpr int operator()(Args&&...) const;
};

union union0 {
    unsigned int x;
    unsigned long long int y;
    template <class... Args> constexpr int operator()(Args&&...);
    template <class... Args> constexpr int operator()(Args&&...) const;
};

struct final_class final {
    template <class... Args> constexpr int operator()(Args&&...);
    template <class... Args> constexpr int operator()(Args&&...) const;
};

int main(int argc, char* argv[]) {
     std::cout << is_functor<int>::value;
     std::cout << is_functor<class0>::value;
     std::cout << is_functor<class1>::value;
     std::cout << is_functor<class2>::value;
     std::cout << is_functor<class3>::value;
     std::cout << is_functor<union0>::value;
     std::cout << is_functor<final_class>::value << std::endl;
     return 0;
}

Должен выводить 001111X. В идеальном мире X должно быть 1, но я не думаю, что это выполнимо в C++17 (см. Раздел о бонусах).


Изменить:

Этот пост, кажется, представляет стратегию, которая решает проблему. Однако есть ли лучший / более элегантный способ сделать это в C++17?


Бонус:

И в качестве бонуса, есть ли способ заставить его работать с типами final (но это совершенно необязательно и, вероятно, невыполнимо)?

9
Vincent 8 Мар 2018 в 09:01

1 ответ

Лучший ответ

Опираясь на свой ответ на свой ответ на этот вопрос, я смог решить вашу проблему, включая бонусную :-)

Ниже приведен код, опубликованный в другом потоке, плюс некоторые небольшие настройки для получения специального значения, когда объект не может быть вызван. Коду нужен c ++ 17, поэтому в настоящее время нет MSVC ...

#include<utility>

constexpr size_t max_arity = 10;

struct variadic_t
{
};


struct not_callable_t
{
};

namespace detail
{
    // it is templated, to be able to create a
    // "sequence" of arbitrary_t's of given size and
    // hece, to 'simulate' an arbitrary function signature.
    template <size_t>
    struct arbitrary_t
    {
        // this type casts implicitly to anything,
        // thus, it can represent an arbitrary type.
        template <typename T>
        operator T&& ();

        template <typename T>
        operator T& ();
    };

    template <typename F, size_t... Is,
                typename U = decltype(std::declval<F>()(arbitrary_t<Is>{}...))>
    constexpr auto test_signature(std::index_sequence<Is...>)
    {
        return std::integral_constant<size_t, sizeof...(Is)>{};
    }

    template <size_t I, typename F>
    constexpr auto arity_impl(int) -> decltype(test_signature<F>(std::make_index_sequence<I>{}))
    {
        return {};
    }


    template <size_t I, typename F, std::enable_if_t<(I == 0), int> = 0>
    constexpr auto arity_impl(...) {
        return not_callable_t{};
    }

    template <size_t I, typename F, std::enable_if_t<(I > 0), int> = 0>
    constexpr auto arity_impl(...)
    {
        // try the int overload which will only work,
        // if F takes I-1 arguments. Otherwise this
        // overload will be selected and we'll try it 
        // with one element less.
        return arity_impl<I - 1, F>(0);
    }

    template <typename F, size_t MaxArity = 10>
    constexpr auto arity_impl()
    {
        // start checking function signatures with max_arity + 1 elements
        constexpr auto tmp = arity_impl<MaxArity + 1, F>(0);
        if constexpr(std::is_same_v<std::decay_t<decltype(tmp)>, not_callable_t>) {
            return not_callable_t{};
        }
        else if constexpr (tmp == MaxArity + 1)
        {
            // if that works, F is considered variadic
            return variadic_t{};
        }
        else
        {
            // if not, tmp will be the correct arity of F
            return tmp;
        }
    }
}

template <typename F, size_t MaxArity = max_arity>
constexpr auto arity(F&& f) { return detail::arity_impl<std::decay_t<F>, MaxArity>(); }

template <typename F, size_t MaxArity = max_arity>
constexpr auto arity_v = detail::arity_impl<std::decay_t<F>, MaxArity>();

template <typename F, size_t MaxArity = max_arity>
constexpr bool is_variadic_v = std::is_same_v<std::decay_t<decltype(arity_v<F, MaxArity>)>, variadic_t>;

// HERE'S THE IS_FUNCTOR

template<typename T>
constexpr bool is_functor_v = !std::is_same_v<std::decay_t<decltype(arity_v<T>)>, not_callable_t>;

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

constexpr auto lambda_func = [](auto...){};

void test_is_functor() {
    static_assert(!is_functor_v<int>);
    static_assert(!is_functor_v<class0>);
    static_assert(is_functor_v<class1>);
    static_assert(is_functor_v<class2>);
    static_assert(is_functor_v<class3>);
    static_assert(is_functor_v<union0>);
    static_assert(is_functor_v<final_class>);
    static_assert(is_functor_v<decltype(lambda_func)>);
}

См. Также работающий пример здесь.

3
florestan 23 Мар 2018 в 11:17