如何为模板化运算符()写出最好的is_callable特性

时间:2012-02-10 16:34:49

标签: c++ c++11 sfinae

我的is_callable trait定义如下:

#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

的模板化operator()
template<typename T>
T operator()()
{
  // ...
}

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

我知道这种情况很少见,但我想问一下是否有办法检测没有参数和一个或多个模板参数的模板化operator()的存在。

2 个答案:

答案 0 :(得分:4)

如果您事先知道operator()不会超载,您可以尝试获取其地址。如果operator() 可能重载,那么肯定的结果意味着存在operator()但是否定结果意味着不存在operator(),或者至少有两次重载。

请注意,模板将(如预期的那样)带来几个operator()的重载。但是,如果您确实知道未默认的模板参数的数量,则可以尝试使用operator()<T>的地址(对于某些类型T 希望不会触发SFINAE )。

作为最后一点,我建议不要花费太多时间来尝试检查仿函数(或成员函数,出于同样的原因)而不知道要传递什么参数,就像你已经拥有的那样。 C ++ 11使编写和使用在表达级别起作用的通用代码变得非常容易。

答案 1 :(得分:1)

您正在尝试使用非推断的模板参数检测operator()成员函数模板,这些模板参数根本不是“可调用的”,也有点无意义 - 函数模板应该具有真实性name,因为你的例子确实错过了整个operator事物的要点。但无论如何,让我们解决你的问题。

请允许我在前面加上我正在处理的库解决方案的插件,称为CallableTraits(再次,正在进行的工作)。

虽然你的案例不是由CallableTraits处理的,但是库确实采用了我要描述的技术来解决一个非常类似的问题。该技术完全是黑客攻击,但它符合标准,并且适用于以下平台:

  • GCC 5.2及更高版本
  • Clang 3.5及更高版本
  • Visual Studio 2015 Update 1 - 基本上可以正常工作

注意:Visual Studio 2015 Update 2已损坏,因为它错误地推断了部分特化中的std::index_sequence<I...> ...我提交了错误报告。有关说明,请参阅here

注意:如果您的标准库实现还没有std::disjunction,那么您可以改为使用示例实现here

我将该技术称为模板蠕虫。这是元编程相当于吐出深深的黑暗井,只是为了听到飞溅需要多长时间。

什么是模板蠕虫?

  1. 模板蠕虫是一个可以转换为任何东西的类。
  2. 任何带有模板蠕虫操作数的运算符表达式将始终评估为另一个模板蠕虫。
  3. 模板蠕虫只能在未评估的上下文中使用。换句话说,只有在decltype围绕顶级表达式时才能使用它,就像std::declval<T>()一样。
  4. 模板蠕虫会将自己摆动到不应该的位置,并坚持它能找到的第一种具体类型。以类似的方式,一种真正的蠕虫将在7月的任何一个下午粘在混凝土上。

    为了解决你的问题,我们将从没有参数开始,然后递归地工作到10的任意限制。我们尝试通过函数传递模板蠕虫来调用(潜在的)函数对象 - 样式调用,按模板类型参数(根据您的要求)。

    此代码不考虑INVOKE语义,因为这会占用更多代码。如果您需要使用指向成员函数的指针和指向成员数据的指针,您可以为此推出自己的实现。

    我可能没有覆盖所有操作符,我可能没有正确实现它们,但你会明白这一点。

    最后一件事:

    我知道一个问题。 返回类型不能依赖于dependent name(成员运算符除外)。

    编辑:此外,调用/模板实例化需要SFINAE友好(即没有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'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() {}