首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >callable_traits实现

callable_traits实现
EN

Code Review用户
提问于 2022-01-04 17:55:41
回答 1查看 139关注 0票数 2

更新:这段代码有新版本:v2在这里发布v3在这里发布v4在这里发布

目标:实现任何可调用的特性,返回其对称性、返回类型和参数类型。由于指向数据成员的指针也是可调用的,因此应该对这些指针进行处理(并将其视为0-偶然性)。

代码如下,试试这里。我有以下问题:

  • 当传递不可调用的类型(例如测试代码底部的int )时,如何生成一个很好的编译器错误消息?这些都是在template <typename T> struct callable_traits : callable_traits<decltype(&std::decay_t<T>::operator())>上出现的,但我没有设法测试是否存在一个操作符(),这样就可以发出static_assert
  • 请参阅测试代码中useInFunc()中函数的使用情况。在MSVC上,我需要做traits::template arg<0>而不是traits::arg<0> (在MSVC上不需要输入名称,但是GCC要求这样做,这就是为什么会出现这种情况)。添加template有点困难(因为它是一个依赖类型?)。有什么办法可以避免吗?一般情况下,我想知道为什么我需要添加typename时,在函数内使用,而不是主要用于GCC,以及为什么template为MSVC.
  • 我假设这不适用于重载的函数。我们能抓住它,还是扩展它使它工作?
代码语言:javascript
复制
#pragma once

// derived and extended from https://github.com/kennytm/utils/blob/master/traits.hpp
// and https://stackoverflow.com/a/28213747

#include <tuple>
#include <type_traits>

// get at operator() of any struct/class defining it (this includes lambdas)
template <typename T>
struct callable_traits : callable_traits<decltype(&std::decay_t<T>::operator())>
{};

#define SPEC(cv,unused)                                                         \
/* handle anything with a function-like signature */                            \
template <typename R, typename... Args>                                         \
struct callable_traits<R(Args...) cv>                                           \
{                                                                               \
    using arity = std::integral_constant<std::size_t, sizeof...(Args) >;        \
                                                                                \
    using result_type = R;                                                      \
                                                                                \
    template <std::size_t i>                                                    \
    using arg = typename std::tuple_element<i, std::tuple<Args...,void>>::type; \
};                                                                              \
/* handle pointers to data members */                                           \
template <typename C, typename R>                                               \
struct callable_traits<R C::* cv>                                               \
{                                                                               \
    using arity = std::integral_constant<std::size_t, 0 >;                      \
                                                                                \
    using result_type = R;                                                      \
                                                                                \
    template <std::size_t i>                                                    \
    using arg = typename std::tuple_element<i, std::tuple<void>>::type;         \
};                                                                              \
/* handle pointers to member functions, all possible iterations of reference and noexcept */    \
template <typename C, typename R, typename... Args>                                             \
struct callable_traits<R(C::*)(Args...) cv>             : public callable_traits<R(Args...)> {};\
template <typename C, typename R, typename... Args>                                             \
struct callable_traits<R(C::*)(Args...) cv &>           : public callable_traits<R(Args...)> {};\
template <typename C, typename R, typename... Args>                                             \
struct callable_traits<R(C::*)(Args...) cv &&>          : public callable_traits<R(Args...)> {};\
template <typename C, typename R, typename... Args>                                             \
struct callable_traits<R(C::*)(Args...) cv noexcept>    : public callable_traits<R(Args...)> {};\
template <typename C, typename R, typename... Args>                                             \
struct callable_traits<R(C::*)(Args...) cv & noexcept>  : public callable_traits<R(Args...)> {};\
template <typename C, typename R, typename... Args>                                             \
struct callable_traits<R(C::*)(Args...) cv && noexcept> : public callable_traits<R(Args...)> {};

// cover all const and volatile permutations
SPEC(, )
SPEC(const, )
SPEC(volatile, )
SPEC(const volatile, )

// handle pointers to free functions
template <typename R, typename... Args>
struct callable_traits<R(*)(Args...)> : public callable_traits<R(Args...)> {};

测试代码:

代码语言:javascript
复制
void test(int)
{

}

template <typename Func>
void useInFunc(Func)
{
    using traits = callable_traits<Func>;
    static_assert(std::is_same_v<const char*, typename traits::result_type>, "");
    static_assert(std::is_same_v<const int&, typename traits::template arg<0>>, "");
}

struct tester
{
    void yolo(char)
    {

    }

    std::string field;
};

int main(int argc, char **argv)
{
    auto lamb = [](const int& in_) noexcept {return "ret"; };

    using traits = callable_traits<decltype(lamb)>;
    static_assert(std::is_same_v<const char*, traits::result_type>, "");
    static_assert(std::is_same_v<const int&, traits::arg<0>>, "");

    useInFunc(lamb);

    using traits2 = callable_traits<decltype(&test)>;
    static_assert(std::is_same_v<void, traits2::result_type>, "");
    static_assert(std::is_same_v<int, traits2::arg<0>>, "");

    using traits3 = callable_traits<decltype(&tester::yolo)>;
    static_assert(std::is_same_v<void, traits3::result_type>, "");
    static_assert(std::is_same_v<char, traits3::arg<0>>, "");

    using traits4 = callable_traits<decltype(&tester::field)>;
    static_assert(std::is_same_v<std::string, traits4::result_type>, "");
    static_assert(std::is_same_v<void, traits4::arg<0>>, "");

    using traits5 = callable_traits<std::add_rvalue_reference_t<decltype(lamb)>>;
    static_assert(std::is_same_v<const char*, traits5::result_type>, "");
    static_assert(std::is_same_v<const int&, traits5::arg<0>>, "");

    using traits6 = callable_traits<std::add_lvalue_reference_t<decltype(lamb)>>;
    static_assert(std::is_same_v<const char*, traits6::result_type>, "");
    static_assert(std::is_same_v<const int&, traits6::arg<0>>, "");

    /*using traits7 = callable_traits<int>;
    static_assert(std::is_same_v<const char*, traits7::result_type>, "");
    static_assert(std::is_same_v<const int&, traits7::arg<0>>, "");*/

    return 0;
}
EN

回答 1

Code Review用户

回答已采纳

发布于 2022-01-05 01:45:44

缺少对某些类型的可调用

的支持

如果您试图获得独立函数(即noexcept )的可调用特性,则无法编译。这很容易通过添加以下内容来解决:

代码语言:javascript
复制
template <typename R, typename... Args> \
struct callable_traits<R(Args...) cv noexcept>: public callable_traits<R(Args...) cv> {}; \

正如Jarod42所提到的,您的代码也不处理各种函数。在代码中引用的StackOverflow帖子中,这个答案展示了一种添加对此的支持的方法。

不要在元组

中添加void

不应该需要将void附加到您希望获得元素类型的元组中。只需写:

代码语言:javascript
复制
template <std::size_t i> \
using arg = typename std::tuple_element<i, std::tuple<Args...>>::type; \

调用方应使用arity检查函数是否接受任何参数。

为什么要检查数据成员?

我不明白为什么会有与数据成员的指针匹配的重载。它们是不可调用的,所以我会删除模板重载。

使arity成为值

与其将arity作为一种类型,不如考虑让它成为一个值,如下所示:

代码语言:javascript
复制
static constexpr auto arity = sizeof...(Args);

这避免了在之后添加::value的需要。

可以避免宏吗?

如果你不需要一个宏就能处理函数的简历--那就太好了。还有一些重复处理常规函数和noexcept函数。也许可以通过采用四个阶段的方法来实现:

  1. 得到真正的可呼叫性。如果传递一个(指向成员的指针)函数,只需按原样返回它。如果它是函子,返回它的operator()
  2. 删除cv-限定符和noexcept
  3. 如果它是指向成员变量的指针,则返回函数类型,否则按原样返回它。
  4. 提供最终非noexcept函数类型的特征。

这可能不容易。特别是,没有std::remove_noexcept,所以您可能不得不使用你自己去实现

处理重载函数

除非您已经知道参数的类型和cv限定,否则您是不可能这样做的。否则,编译器无法解析重载。如果您知道参数,可以使用std::invoke_result获取唯一丢失的信息(即返回类型)。

--为什么对templatetypename

看似不必要的需求

实际上,有时编译器无法预先知道标识符是否引用了类型、变量或函数。解析模板定义时,它没有实例化模板。因此,它无法确切地知道std::tuple_element<i, std::tuple<Args...>>::type将是什么。您可以通过在前面添加typename来消除这种歧义。类似地,如果编译器无法推断这一点,则可能需要在某些地方添加template,但在本例中使用的MSVC版本似乎存在缺陷。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/272633

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档