Подтвердить что ты не робот

Как написать наилучший возможный is_callable признак шаблона оператора()

У меня есть is_callable признак, подобный этому:

#ifndef IS_CALLABLE_HPP
#define IS_CALLABLE_HPP

#include <type_traits>

namespace is_callable_detail
{
    struct no   {};
    struct yes  { no x[2]; };

    template<bool CallableArgs, typename Callable, typename ReturnType, typename ...Args>
    struct check_return
    {
        static const bool value = std::is_convertible<decltype(std::declval<Callable>()(std::declval<Args>()...)), ReturnType>::value;
    };

    template<typename Callable, typename ReturnType, typename ...Args>
    struct check_return<false, Callable, ReturnType, Args...>
    {
        static const bool value = false;
    };
}

template<typename Callable, typename Function>
struct is_callable;

template<typename Callable, typename ReturnType, typename ...Args>
struct is_callable<Callable, ReturnType(Args...)>
{
    private:
        template<typename T>
        static is_callable_detail::yes check(decltype(std::declval<T>()(std::declval<Args>()...)) *);
        template<typename T>
        static is_callable_detail::no  check(...);

        static const bool value_args = sizeof(check<Callable>(nullptr)) == sizeof(is_callable_detail::yes);
        static const bool value_return = is_callable_detail::check_return<value_args, Callable, ReturnType, Args...>::value;
    public:
        static const bool value = value_args && value_return;
};

#endif // IS_CALLABLE_HPP

Мой вопрос заключается в том, как обнаружить шаблонный оператор(), который не имеет аргументов и имеет только тип возврата T

template<typename T>
T operator()()
{
  // ...
}

или

template<typename T, typename U>
auto operator()() -> decltype(std::declval<T>() + std::declval<U>())
{
  // ...
}

Я знаю, что эти ситуации встречаются редко, но я хотел спросить, есть ли способ обнаружить наличие шаблонного оператора() без аргументов и с одним или несколькими аргументами шаблона.

4b9b3361

Ответ 1

Если вы заранее знаете, что operator() не будет перегружен, вы можете попытаться принять его адрес. Если operator() возможно перегружен, то положительный результат будет означать, что присутствует operator(), но отрицательный результат будет означать, что либо нет operator(), либо, по крайней мере, две перегрузки.

Обратите внимание, что шаблон (как и ожидалось) приведет к нескольким перегрузкам operator(). Однако, если вы знаете количество параметров шаблона, которые не дефолты, вы можете попробовать взять адрес operator()<T> (для некоторого типа T, который, надеюсь, не вызовет SFINAE).

Как последнее замечание, я бы посоветовал не пытаться тратить слишком много времени, пытаясь проверить функторы (или функции-члены по тем же причинам), не зная, какие аргументы проходят, как и то, что у вас уже есть. С++ 11 позволяет легко писать и использовать общий код, который функционирует на уровне выражения.

Ответ 2

Вы пытаетесь обнаружить шаблон функции члена operator() с параметрами не выводимых параметров шаблона, которые на самом деле не являются "вызываемыми", а также являются беспредметными - вместо шаблона функции должно быть настоящее имя, потому что ваш пример действительно пропускает точку всей вещи operator. Но пусть все равно разрешит вашу проблему.

Позвольте мне прочитать это с помощью подключаемого модуля для решения библиотеки, над которым я работаю, называемый CallableTraits (опять же, работа продолжается),

В то время как ваш случай не обрабатывается CallableTraits, библиотека использует технику, которую я собираюсь описать, чтобы решить очень похожую проблему. Этот метод является полным взломом, но он стандартно совместим и работает для меня на следующих платформах:

  • GCC 5.2 и более поздние версии
  • Clang 3.5 и более поздние версии
  • Обновление Visual Studio 2015 1 - в основном работает

Примечание. Обновление Visual Studio 2015 Update 2 нарушено, поскольку оно неправильно выводит std::index_sequence<I...> в частичную специализацию... Я подал отчет об ошибке. См. здесь для описания.

Примечание. Если ваша стандартная реализация библиотеки не имеет std::disjunction, то вы можете вместо этого использовать примерную реализацию здесь.

Я вызываю метод червя шаблона. Это метапрограммирующий эквивалент плевки в глубокий, темный колодец, просто чтобы услышать, сколько времени потребуется, чтобы всплеск.

Что такое шаблонный червь?

  • Шаблонный червь - это класс, который конвертируется во что угодно и все.
  • Любые операторные выражения с шаблоном-червячным операндом всегда будут оцениваться другим червем шаблона.
  • Шаблонный червь может использоваться только в неоценимом контексте. Другими словами, вы можете использовать его только тогда, когда decltype окружает выражение верхнего уровня, точно так же, как std::declval<T>().

Шаблонный червь вибрирует в местах, которые он не должен быть, и придерживается первого конкретного типа, который он может найти. Аналогичным образом настоящий герой будет придерживаться бетона в любой день июля.

Чтобы решить вашу проблему, мы начнем с каких-либо аргументов, а затем рекурсивно работаем до произвольного предела в 10. Мы пытаемся сделать вызов объекту функции (потенциала), пытаясь передать шаблонный червь посредством функции- вызов стиля, и аргумент типа шаблона (в соответствии с вашими требованиями).

Этот код не учитывает семантику INVOKE, потому что это занимает значительно больше кода. Если вам нужно это для работы с указателями-членами-функциями и данными-указателями-членами, вы можете использовать для этого свою собственную реализацию.

Возможно, я не охватил все операторы, и я мог бы не реализовывать их все правильно, но вы увидите точку.

Последнее:

Я знаю один улов. Тип возврата не может зависеть от зависимого имени (кроме операторов-членов).

Изменить: Кроме того, создание экземпляра/шаблона должно быть удобным для SFINAE (т.е. no static_assert s).

Без дальнейших церемоний, вот ваше автономное решение (хотя вы, возможно, пожелаете, чтобы вы не спрашивали):

#include <utility>
#include <type_traits>

namespace detail {

    //template_worm CANNOT be used in evaluated contexts
    struct template_worm {

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

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

        template_worm() = default;

#ifndef _MSC_VER

        // MSVC doesn't like this... because it can deduce void?
        // Whatever, we can do without it on Windows
        template<typename... T>
        template_worm(T&&...);

#endif //_MSC_VER

        template_worm operator+() const;
        template_worm operator-() const;
        template_worm operator*() const;
        template_worm operator&() const;
        template_worm operator!() const;
        template_worm operator~() const;
        template_worm operator()(...) const;
    };

#define TEMPLATE_WORM_BINARY_OPERATOR(...)                                 \
                                                                           \
    template<typename T>                                                   \
    constexpr inline auto                                                  \
    __VA_ARGS__ (template_worm, T&&) -> template_worm {                    \
        return template_worm{};                                            \
    }                                                                      \
                                                                           \
    template<typename T>                                                   \
    constexpr inline auto                                                  \
    __VA_ARGS__ (T&&, template_worm) -> template_worm {                    \
        return template_worm{};                                            \
    }                                                                      \
                                                                           \
    constexpr inline auto                                                  \
    __VA_ARGS__ (template_worm, template_worm) -> template_worm {          \
        return template_worm{};                                            \
    }                                                                      \
    /**/

    TEMPLATE_WORM_BINARY_OPERATOR(operator+)
    TEMPLATE_WORM_BINARY_OPERATOR(operator-)
    TEMPLATE_WORM_BINARY_OPERATOR(operator/)
    TEMPLATE_WORM_BINARY_OPERATOR(operator*)
    TEMPLATE_WORM_BINARY_OPERATOR(operator==)
    TEMPLATE_WORM_BINARY_OPERATOR(operator!=)
    TEMPLATE_WORM_BINARY_OPERATOR(operator&&)
    TEMPLATE_WORM_BINARY_OPERATOR(operator||)
    TEMPLATE_WORM_BINARY_OPERATOR(operator|)
    TEMPLATE_WORM_BINARY_OPERATOR(operator&)
    TEMPLATE_WORM_BINARY_OPERATOR(operator%)
    TEMPLATE_WORM_BINARY_OPERATOR(operator,)
    TEMPLATE_WORM_BINARY_OPERATOR(operator<<)
    TEMPLATE_WORM_BINARY_OPERATOR(operator>>)
    TEMPLATE_WORM_BINARY_OPERATOR(operator<)
    TEMPLATE_WORM_BINARY_OPERATOR(operator>)

    template<std::size_t Ignored>
    using worm_arg = template_worm const &;

    template<typename T>
    struct success {};

    struct substitution_failure {};

    template<typename F, typename... Args>
    struct invoke_test {

        template<typename T, typename... Rgs>
        auto operator()(T&& t, Rgs&&... rgs) const ->
            success<decltype(std::declval<T&&>()(std::forward<Rgs>(rgs)...))>;

        auto operator()(...) const->substitution_failure;

        static constexpr int arg_count = sizeof...(Args);
    };

    // force_template_test doesn't exist in my library
    // solution - it exists to please OP
    template<typename... Args>
    struct force_template_test {

        template<typename T>
        auto operator()(T&& t) const ->
            success<decltype(std::declval<T&&>().template operator()<Args...>())>;

        auto operator()(...) const->substitution_failure;
    };

    template<typename T, typename... Args>
    struct try_invoke {

        using test_1 = invoke_test<T, Args...>;

        using invoke_result = decltype(test_1{}(
            ::std::declval<T>(),
            ::std::declval<Args>()...
            ));

        using test_2 = force_template_test<Args...>;

        using force_template_result = decltype(test_2{}(std::declval<T>()));

        static constexpr bool value =
            !std::is_same<invoke_result, substitution_failure>::value
            || !std::is_same<force_template_result, substitution_failure>::value;

        static constexpr int arg_count = test_1::arg_count;
    };

    template<typename T>
    struct try_invoke<T, void> {
        using test = invoke_test<T>;
        using result = decltype(test{}(::std::declval<T>()));
        static constexpr bool value = !std::is_same<result, substitution_failure>::value;
        static constexpr int arg_count = test::arg_count;
    };

    template<typename U, std::size_t Max, typename = int>
    struct min_args;

    struct sentinel {};

    template<typename U, std::size_t Max>
    struct min_args<U, Max, sentinel> {
        static constexpr bool value = true;
        static constexpr int arg_count = -1;
    };

    template<typename U, std::size_t Max, std::size_t... I>
    struct min_args<U, Max, std::index_sequence<I...>> {

        using next = typename std::conditional<
            sizeof...(I)+1 <= Max,
            std::make_index_sequence<sizeof...(I)+1>,
            sentinel
        >::type;

        using result_type = std::disjunction<
            try_invoke<U, worm_arg<I>...>,
            min_args<U, Max, next>
        >;

        static constexpr bool value = result_type::value;
        static constexpr int arg_count = result_type::arg_count;
    };

    template<typename U, std::size_t Max>
    struct min_args<U, Max, void> {

        using result_type = std::disjunction<
            try_invoke<U, void>,
            min_args<U, Max, std::make_index_sequence<1>>
        >;

        static constexpr int arg_count = result_type::arg_count;
        static constexpr bool value = result_type::value;
    };

    template<typename T, std::size_t SearchLimit>
    using min_arity = std::integral_constant<int,
        min_args<T, SearchLimit, void>::arg_count>;
}

// Here you go.
template<typename T>
using is_callable = std::integral_constant<bool,
    detail::min_arity<T, 10>::value >= 0>;

// This matches OP first example.
struct Test1 {

    template<typename T>
    T operator()() {
        return{};
    }
};

// Yup, it "callable", at least by OP definition...
static_assert(is_callable<Test1>::value, "");

// This matches OP second example.
struct Test2 {

    template<typename T, typename U>
    auto operator()() -> decltype(std::declval<T>() + std::declval<U>()) {
        return{};
    }
};

// Yup, it "callable", at least by OP definition...
static_assert(is_callable<Test2>::value, "");

// ints aren't callable, of course
static_assert(!is_callable<int>::value, "");

int main() {}