首页 > 解决方案 > 是否可以定义一个包含函数和 lambda 的可调用概念?

问题描述

我想定义一个可以接受所有可调用对象的概念。这是我到目前为止所做的:

template<typename F>
concept Func = std::is_function_v<std::remove_pointer_t<std::decay_t<F>>> || (requires (F f) {
    std::is_function_v<decltype(f.operator())>;
});

bool is_callable(Func auto&&) {
    return true;
}

bool is_callable(auto&&) {
    return false;
}

然而,如果我定义这些:

auto f = [](auto a, auto b, auto c, auto d, auto e) {
    return a * b * c * d * e;
};

int g(int a, int b) {
    return a + b;
}

is_callable(g)true但是is_callable(f)false,它没有用(我希望两者都返回true)。

所以我试图看看以下是否会编译:

decltype(f.operator()) // Reference to non-static member function must be called
decltype(&f.operator()) // Cannot create a non-constant pointer to member function
decltype(f::operator()) // 'f' is not a class, namespace, or enumeration
decltype(&f::operator()) // same as previously

它给了我你可以看到的错误,作为对这 4 行的评论。

有没有办法检查 f 是否有一个有效的函子,这意味着 f 是一个 lambda?

我想要实现的目标有更好的解决方案吗?

标签: c++lambdatypetraitsc++20c++-concepts

解决方案


你想要的是不可能的(或者是一个好主意,但现在没关系)。

C++ 中按名称命名的“函数”可能代表许多函数。它通过模板参数推导表示重载、模板实例化等。但是要获得函数指针,您需要仔细查看所有这些。如果名称表示重载集,要获得指针,您必须将该名称强制转换为特定的重载。如果名称表示模板,则必须提供模板参数来表示特定的实例化。

这意味着,当您的假设is_callable概念在函数指针类型上被调用时,所有重载决议和模板替换都已经发生。它被赋予一个指向特定的、定义良好的代码段的指针,该代码段可以使用由该指针类型定义的签名来调用。

函数对象都不是这种情况。仿函数(无论是由 C++ lambda 表达式生成还是只是手写类型)只不过是一种具有operator()重载的类型。重载只是一个函数名,与任何其他名称完全一样:受重载解析和模板替换规则的约束。

C++ 不允许你问“这是一个名字;我可以用一些东西来称呼它?”

从广义上讲,这不是一个有用的问题。

无论您是使用这种“可调用”概念进行柯里化还是使用什么,在某些时候,某些代码将使用一组参数调用某个函数,最终将级联到调用具有另一组参数定义的给定函数通过一些过程。这就是您需要约束给定可调用对象的时候。

在构建 curried callable 的站点上限制函数是没有用的。您不知道参数和返回值之间是否存在类型不匹配,或者类似的情况。只有当您获得一组用于调用 curried 可调用对象的参数时,您才会知道这一点。那是您可以计算参数以最终调用正确函数的地方,因此应该进行约束。


推荐阅读