c++ - 使用 C++17 检测仿函数的类型特征?

标签 c++ c++17 template-meta-programming functor sfinae

问题描述:

C++17介绍 std::invocable<F, Args...> ,这很适合检测类型...是否可以使用给定的参数调用。但是,对于仿函数的任何参数是否有办法做到这一点(因为标准库的现有特征的组合已经允许检测函数、函数指针、函数引用、成员函数......)?

换句话说,如何实现下面的类型特征?

template <class F>
struct is_functor {
    static constexpr bool value = /*using F::operator() in derived class works*/;
};

使用示例:

#include <iostream>
#include <type_traits>

struct class0 {
    void f();
    void g();
};

struct class1 {
    void f();
    void g();
    void operator()(int);
};

struct class2 {
    void operator()(int);
    void operator()(double);
    void operator()(double, double) const noexcept;
};

struct class3 {
    template <class... Args> constexpr int operator()(Args&&...);
    template <class... Args> constexpr int operator()(Args&&...) const;
};

union union0 {
    unsigned int x;
    unsigned long long int y;
    template <class... Args> constexpr int operator()(Args&&...);
    template <class... Args> constexpr int operator()(Args&&...) const;
};

struct final_class final {
    template <class... Args> constexpr int operator()(Args&&...);
    template <class... Args> constexpr int operator()(Args&&...) const;
};

int main(int argc, char* argv[]) {
     std::cout << is_functor<int>::value;
     std::cout << is_functor<class0>::value;
     std::cout << is_functor<class1>::value;
     std::cout << is_functor<class2>::value;
     std::cout << is_functor<class3>::value;
     std::cout << is_functor<union0>::value;
     std::cout << is_functor<final_class>::value << std::endl;
     return 0;
}

应该输出001111X .在理想世界中,X应该是 1 ,但我认为这在 C++17 中不可行(见奖金部分)。


编辑:

This post似乎提出了解决问题的策略。但是,在 C++17 中会有更好/更优雅的方法吗? ?


奖励:

作为奖励,是否有办法让它在 final 上运行?类型(但这完全是可选的,可能不可行)?

最佳答案

基于我对 this qustion 的回答,我能够解决你的问题,包括奖金 :-)

以下是在另一个线程中发布的代码加上一些小的调整,以便在无法调用对象时获得特殊值。代码需要 c++17,所以目前没有 MSVC...

#include<utility>

constexpr size_t max_arity = 10;

struct variadic_t
{
};


struct not_callable_t
{
};

namespace detail
{
    // it is templated, to be able to create a
    // "sequence" of arbitrary_t's of given size and
    // hece, to 'simulate' an arbitrary function signature.
    template <size_t>
    struct arbitrary_t
    {
        // this type casts implicitly to anything,
        // thus, it can represent an arbitrary type.
        template <typename T>
        operator T&& ();

        template <typename T>
        operator T& ();
    };

    template <typename F, size_t... Is,
                typename U = decltype(std::declval<F>()(arbitrary_t<Is>{}...))>
    constexpr auto test_signature(std::index_sequence<Is...>)
    {
        return std::integral_constant<size_t, sizeof...(Is)>{};
    }

    template <size_t I, typename F>
    constexpr auto arity_impl(int) -> decltype(test_signature<F>(std::make_index_sequence<I>{}))
    {
        return {};
    }


    template <size_t I, typename F, std::enable_if_t<(I == 0), int> = 0>
    constexpr auto arity_impl(...) {
        return not_callable_t{};
    }

    template <size_t I, typename F, std::enable_if_t<(I > 0), int> = 0>
    constexpr auto arity_impl(...)
    {
        // try the int overload which will only work,
        // if F takes I-1 arguments. Otherwise this
        // overload will be selected and we'll try it 
        // with one element less.
        return arity_impl<I - 1, F>(0);
    }

    template <typename F, size_t MaxArity = 10>
    constexpr auto arity_impl()
    {
        // start checking function signatures with max_arity + 1 elements
        constexpr auto tmp = arity_impl<MaxArity + 1, F>(0);
        if constexpr(std::is_same_v<std::decay_t<decltype(tmp)>, not_callable_t>) {
            return not_callable_t{};
        }
        else if constexpr (tmp == MaxArity + 1)
        {
            // if that works, F is considered variadic
            return variadic_t{};
        }
        else
        {
            // if not, tmp will be the correct arity of F
            return tmp;
        }
    }
}

template <typename F, size_t MaxArity = max_arity>
constexpr auto arity(F&& f) { return detail::arity_impl<std::decay_t<F>, MaxArity>(); }

template <typename F, size_t MaxArity = max_arity>
constexpr auto arity_v = detail::arity_impl<std::decay_t<F>, MaxArity>();

template <typename F, size_t MaxArity = max_arity>
constexpr bool is_variadic_v = std::is_same_v<std::decay_t<decltype(arity_v<F, MaxArity>)>, variadic_t>;

// HERE'S THE IS_FUNCTOR

template<typename T>
constexpr bool is_functor_v = !std::is_same_v<std::decay_t<decltype(arity_v<T>)>, not_callable_t>;

鉴于您问题中的类,以下编译成功(您甚至可以使用可变参数 lambda:

constexpr auto lambda_func = [](auto...){};

void test_is_functor() {
    static_assert(!is_functor_v<int>);
    static_assert(!is_functor_v<class0>);
    static_assert(is_functor_v<class1>);
    static_assert(is_functor_v<class2>);
    static_assert(is_functor_v<class3>);
    static_assert(is_functor_v<union0>);
    static_assert(is_functor_v<final_class>);
    static_assert(is_functor_v<decltype(lambda_func)>);
}

另请参阅运行示例 here .

关于c++ - 使用 C++17 检测仿函数的类型特征?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49166422/

相关文章:

c++ - 我想我不明白 makefile 是如何工作的

c++ - std::any 的存储对象类型的可能性

c++ - OpenGL 映射纹理到球体

c++ - 在知道可调用参数之前如何约束惰性组合?

c++ - 引入typetraits后非模板的模板定义

c++ - 基于模板参数初始化静态字符

c++ - 如何为模板类型创建别名?

c++ - 如何在编译时获得多维 std::vector 的深度?

c++ - istream::ignore 和 getline() 混淆

c# - 在第一次使用之前声明变量有什么缺点?