如何为模板化的operator()编写最好的is_callable特征

发布于 2025-01-04 14:00:48 字数 1876 浏览 0 评论 0原文

我有这样定义的 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>())
{
  // ...
}

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

I have is_callable trait defined like this:

#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

My question is how to detect templated operator() which doesn't have arguments and has only return type T

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

or

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

I know that this situations are rare, but I wanted to ask is there any way to detect presence of templated operator() with no arguments and with one or more template arguments.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

懷念過去 2025-01-11 14:00:48

如果你事先知道operator()不会被重载,你可以尝试获取它的地址。如果operator()可能重载,则正结果意味着存在operator(),但负结果意味着存在要么不存在 operator(),要么至少存在两个重载。

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

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

If you know in advance operator() is not going to be overloaded, you can try to take its address. If operator() is possibly overloaded, then a positive result would mean that there is an operator() present but a negative result would mean that either no operator() is present, or at least two overloads are.

Notice that a template will (as expected) bring several overloads of operator(). However, if you do know the number of template parameters that are not defaulted you can try taking the address of operator()<T> (for some type T that hopefully won't trigger SFINAE).

As a final note, I'd suggest not trying to spend too much time trying to inspect functors (or member functions, for the same reasons) without knowing what arguments to pass, just like what you already have. C++11 makes it very easy to write and use generic code that functions at the expression level.

寂寞美少年 2025-01-11 14:00:48

您正在尝试检测具有非推导模板参数的 operator() 成员函数模板,这根本不是真正的“可调用”,而且也是毫无意义的 - 函数模板应该具有一个真实的名字,因为你的例子确实没有抓住整个operator的要点。但无论如何,我们还是要解决你的问题。

请允许我用一个我正在开发的库解决方案的插件作为序言,名为 CallableTraits(同样,一项正在进行的工作)。

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

  • GCC 5.2 和更高版本
  • Clang 3.5 和更高版本
  • Visual Studio 2015 Update 1 - 基本上有效

注意:Visual Studio 2015 Update 2 已损坏,因为它不正确在部分专业化中推导出 std::index_sequence...我提交了一份错误报告。请参阅此处了解说明。

注意:如果您的标准库实现还没有 std::disjunction 那么您可以使用示例实现 这里 代替。

我将该技术称为模板蠕虫。这在元编程中相当于往一口又深又黑的井里吐口水,只是为了听听它需要多长时间才能溅出来。

什么是模板蠕虫?

  1. 模板蠕虫是一个可以转换为任何内容的类。
  2. 任何具有模板蠕虫操作数的运算符表达式将始终计算为另一个模板蠕虫。
  3. 模板蠕虫只能在未评估的环境中使用。换句话说,只有当 decltype 包围顶级表达式时才能使用它,就像 std::declval() 一样。

模板蠕虫将自己扭动到不应该去的地方,并坚持它能找到的第一个具体类型。以类似的方式,真正的蠕虫会在七月的任何一个下午粘在混凝土上。

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

此代码不考虑 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() {}

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<I...> 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<T>().

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 <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() {}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文