How to write the best possible is_callable trait for templated operator()

后端 未结 2 886
挽巷
挽巷 2020-12-17 18:58

I have is_callable trait defined like this:

#ifndef IS_CALLABLE_HPP
#define IS_CALLABLE_HPP

#include 

namespace is_callable_detail
{
            


        
2条回答
  •  别那么骄傲
    2020-12-17 19:11

    You are trying to detect an operator() member function template with non-deduced template parameters, which isn't really "callable" at all, and also is kind of pointless - the function template should instead have a real name, because your example really misses the point of the whole operator thing. But let's solve your problem anyway.

    Allow me to preface this with a plug for a library solution I'm working on, called CallableTraits (again, a work in progress).

    While your case is not handled by CallableTraits, the library does employ a technique I'm about to describe to solve a very similar problem. The technique is a total hack, but it's standard compliant, and works for me on the following platforms:

    • GCC 5.2 and later
    • Clang 3.5 and later
    • Visual Studio 2015 Update 1 - basically works

    Note: Visual Studio 2015 Update 2 is broken, because it incorrectly deduces std::index_sequence in partial specializations... I filed a bug report. See here for a description.

    Note: If your standard library implementation doesn't have std::disjunction yet then you can use the sample implementation here instead.

    I call the technique the template worm. It's the metaprogramming equivalent of spitting into a deep, dark well, just to hear how long it takes to splash.

    What is a template worm?

    1. A template worm is a class that is convertible to anything and everything.
    2. Any operator expressions with a template worm operand will always evaluate to another template worm.
    3. A template worm can only be used in an unevaluated context. In other words, you can only use it when a decltype surrounds the top-level expression, just like std::declval().

    A template worm wiggles itself into places it isn't supposed to be, and sticks to the first concrete type it can find. In a similar fashion, a real worm will stick to the concrete on any afternoon in July.

    To solve your problem, we will start with no arguments, then recursively work up to an arbitrary limit of 10. We try to make the call to the (potential) function object by trying to pass the template worm by by function-style invocation, AND by template type argument (per your requirements).

    This code doesn't account for INVOKE semantics, because that takes signifcantly more code. If you need this to work with pointers-to-member-functions and pointers-to-member-data, you can roll your own implementation for that.

    I might not have covered all the operators, and I might not have implemented them all correctly, but you'll see the point.

    One last thing:

    I know of one catch. The return type can't depend on a dependent name (other than member operators).

    Edit: Also, invocation/template instantiation needs to be SFINAE-friendly (i.e. no static_asserts).

    Without further ado, here is your standalone solution (although you might wish you hadn't asked):

    #include 
    #include 
    
    namespace detail {
    
        //template_worm CANNOT be used in evaluated contexts
        struct template_worm {
    
            template
            operator T& () const;
    
            template
            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
            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                                                   \
        constexpr inline auto                                                  \
        __VA_ARGS__ (template_worm, T&&) -> template_worm {                    \
            return template_worm{};                                            \
        }                                                                      \
                                                                               \
        template                                                   \
        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
        using worm_arg = template_worm const &;
    
        template
        struct success {};
    
        struct substitution_failure {};
    
        template
        struct invoke_test {
    
            template
            auto operator()(T&& t, Rgs&&... rgs) const ->
                success()(std::forward(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
        struct force_template_test {
    
            template
            auto operator()(T&& t) const ->
                success().template operator()())>;
    
            auto operator()(...) const->substitution_failure;
        };
    
        template
        struct try_invoke {
    
            using test_1 = invoke_test;
    
            using invoke_result = decltype(test_1{}(
                ::std::declval(),
                ::std::declval()...
                ));
    
            using test_2 = force_template_test;
    
            using force_template_result = decltype(test_2{}(std::declval()));
    
            static constexpr bool value =
                !std::is_same::value
                || !std::is_same::value;
    
            static constexpr int arg_count = test_1::arg_count;
        };
    
        template
        struct try_invoke {
            using test = invoke_test;
            using result = decltype(test{}(::std::declval()));
            static constexpr bool value = !std::is_same::value;
            static constexpr int arg_count = test::arg_count;
        };
    
        template
        struct min_args;
    
        struct sentinel {};
    
        template
        struct min_args {
            static constexpr bool value = true;
            static constexpr int arg_count = -1;
        };
    
        template
        struct min_args> {
    
            using next = typename std::conditional<
                sizeof...(I)+1 <= Max,
                std::make_index_sequence,
                sentinel
            >::type;
    
            using result_type = std::disjunction<
                try_invoke...>,
                min_args
            >;
    
            static constexpr bool value = result_type::value;
            static constexpr int arg_count = result_type::arg_count;
        };
    
        template
        struct min_args {
    
            using result_type = std::disjunction<
                try_invoke,
                min_args>
            >;
    
            static constexpr int arg_count = result_type::arg_count;
            static constexpr bool value = result_type::value;
        };
    
        template
        using min_arity = std::integral_constant::arg_count>;
    }
    
    // Here you go.
    template
    using is_callable = std::integral_constant::value >= 0>;
    
    // This matches OP's first example.
    struct Test1 {
    
        template
        T operator()() {
            return{};
        }
    };
    
    // Yup, it's "callable", at least by OP's definition...
    static_assert(is_callable::value, "");
    
    // This matches OP's second example.
    struct Test2 {
    
        template
        auto operator()() -> decltype(std::declval() + std::declval()) {
            return{};
        }
    };
    
    // Yup, it's "callable", at least by OP's definition...
    static_assert(is_callable::value, "");
    
    // ints aren't callable, of course
    static_assert(!is_callable::value, "");
    
    int main() {}
    

提交回复
热议问题