C++ 如何编写模板化运算符()的最佳可调用特性

C++ 如何编写模板化运算符()的最佳可调用特性,c++,c++11,sfinae,C++,C++11,Sfinae,我有一个可称为特质的定义如下: #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

我有一个可称为特质的定义如下:

#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
#如果NDEF可调用"HPP
#定义是否可调用\u水电站
#包括
名称空间是\u可调用的\u详细信息
{
结构号{};
结构yes{nox[2];};
模板
结构检查返回
{
静态常量布尔值=标准::可转换::值;
};
模板
结构检查返回
{
静态常量布尔值=假;
};
}
模板
结构是可调用的;
模板
结构是可调用的
{
私人:
模板
static可调用\u detail::yes检查(decltype(std::declval()(std::declval()…)*);
模板
静态可调用详细信息::无检查(…);
静态常量bool value_args=sizeof(check(nullptr))==sizeof(is_callable_detail::yes);
静态常量bool value\u return=is\u callable\u detail::check\u return::value;
公众:
静态常量布尔值=值参数和值返回;
};
#endif//是否可调用\u HPP
我的问题是如何检测没有参数且只有返回类型t的模板运算符()

template<typename T>
T operator()()
{
  // ...
}
模板
T运算符()()
{
// ...
}

模板
自动运算符()()->decltype(std::declval()+std::declval())
{
// ...
}

我知道这种情况很少见,但我想问的是,是否有任何方法可以检测是否存在无参数和一个或多个模板参数的模板运算符()。

如果您事先知道
运算符()
不会过载,您可以尝试获取其地址。如果
operator()
可能重载,则肯定的结果将意味着存在
operator()
但否定的结果将意味着不存在
operator()
或至少存在两个重载

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


最后,我建议不要花太多时间在不知道要传递什么参数的情况下检查函子(或成员函数,出于同样的原因),就像您已经拥有的一样。C++11使编写和使用在表达式级别运行的通用代码变得非常容易。

您试图检测一个带有非推断模板参数的
运算符()
成员函数模板,该模板实际上根本不“可调用”,也有点无意义-函数模板应该具有真实名称,因为您的示例确实忽略了整个
操作符的要点。但我们还是要解决你的问题

请允许我先介绍一下我正在开发的一个库解决方案的插件,称为(同样,一个正在进行的工作)

虽然CallableTraits无法处理您的案例,但该库确实使用了我将要描述的一种技术来解决一个非常类似的问题。该技术完全是一种黑客技术,但它符合标准,在以下平台上对我有效:

  • GCC 5.2及更高版本
  • 叮当3.5及更高版本
  • Visual Studio 2015更新1-基本正常
注意:Visual Studio 2015 Update 2已损坏,因为它在部分专门化中错误地推断了
std::index_序列
。。。我提交了一份错误报告。有关说明,请参阅

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

我称这种技术为模板蠕虫。这是元编程,相当于往一口深而暗的井里吐口水,只是为了听它飞溅需要多长时间

什么是模板蠕虫

  • 模板蠕虫是一个可转换为任何内容的类
  • 任何带有模板蠕虫操作数的运算符表达式都将始终计算为另一个模板蠕虫
  • 模板蠕虫只能在未计算的上下文中使用。换句话说,您只能在顶级表达式周围有
    decltype
    时使用它,就像
    std::declval()
    一样 模板蠕虫会将自身摆动到不应该出现的位置,并粘附到它能找到的第一个具体类型。以类似的方式,一条真正的虫子会在七月的任何一个下午粘在混凝土上

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

    这段代码没有考虑语义,因为这需要更多的代码。如果您需要它来处理指向成员函数的指针和指向成员数据的指针,您可以为此推出自己的实现

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

    最后一件事:

    我知道有一个陷阱。返回类型不能依赖于(成员运算符除外)

    编辑:此外,调用/模板实例化需要是SFINAE友好的(即no
    static\u assert
    s)

    下面是您的独立解决方案(尽管您可能希望您没有询问):

    #包括
    #包括
    名称空间详细信息{
    //模板\u蠕虫无法在计算的上下文中使用
    结构模板{
    模板
    算子T&()常数;
    模板
    运算符T&()常量;
    模板_worm()=默认值;
    #ifndef\u理学硕士
    //MSVC不喜欢这个…因为它可以推断出虚空?
    //不管怎样,我们可以不在Windows上使用它
    模板
    模板(T&&…);
    #endif/\u MSC\u VER
    模板_蠕虫操作符+()常量;
    模板蠕虫
    
    template<typename T, typename U>
    auto operator()() -> decltype(std::declval<T>() + std::declval<U>())
    {
      // ...
    }
    
    #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() {}