Как написать лучшую из возможных характеристик is_callable для шаблонного оператора () - PullRequest
11 голосов
/ 10 февраля 2012

У меня есть черта 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>())
{
  // ...
}

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

Ответы [ 2 ]

4 голосов
/ 11 февраля 2012

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

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

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

2 голосов
/ 15 апреля 2016

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

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

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

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

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

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

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

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

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

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

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

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

Возможно, я не охватил все операторы, и, возможно, я не реализовал их все правильно, но вы поймете смысл.

И последнее:

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

Редактировать: Кроме того, вызов / создание шаблона должны быть дружественными к SFINAE (т. Е. Нет static_assert с).

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

#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's first example.
struct Test1 {

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

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

// This matches OP's second example.
struct Test2 {

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

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

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

int main() {}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...