提问人:Jack Sack 提问时间:11/15/2023 最后编辑:Remy LebeauJack Sack 更新时间:11/15/2023 访问量:119
C++ 等同于 Java 的方法引用?
C++ equivalent of Java's method references?
问:
我有一个 ,我想从中检查是否至少有 1 个子向量是空的。所以我有这个:std::vector<std::vector<int>>
std::any_of(vec.begin(), vec.end(), [](const auto& subvec) {
return subvec.empty();
});
C++14 中有这样的东西吗?
std::any_of(vec.begin(), vec.end(), std::vector<int>::empty);
我尝试了上述语法,但没有奏效。正如一位消息人士所声称的那样,在方法前面贴上一个也没有奏效。&
答:
使用 lambda 是可读的方式。
仅供说明,让我们探索其他方式。谓词需要是(来自 cppreference):p
对于类型(可能是 const)的每个参数,表达式必须可转换为 ,其中 是 的值类型,而不管值类别如何,并且不得修改 。因此,不允许参数类型为 ,除非移动等同于副本(自 C++11 起)。
p(v)
bool
v
VT
VT
InputIt
v
VT&
VT
VT
撇开隐式转换不谈,这基本上意味着谓词必须是带有签名的可调用对象。bool (const std::vector<int>&)
std::function
是候选者。它带来了相当大的开销,因为它的主要目的是类型擦除。我们不需要类型擦除,但也有一种机制可以将成员函数指针转换为可调用对象,其中对象被传递给函数。即我们可以将成员函数指针转换为具有签名的东西。到目前为止一切顺利,但这种转换不是隐式的,并且不能很好地与类模板参数推导一起使用。因此,语法相当笨拙:std::function
&std::vector<int>::empty
bool (const std::vector<int>&)
int main () {
std::vector<std::vector<int>> vec;
std::any_of(vec.begin(),vec.end(),std::function<bool(const std::vector<int>&)>(&std::vector<int>::empty));
}
呃......我们可以将成员函数指针转换为具有正确签名的可调用对象,但这并不是这里真正需要的,而且它对简洁的语法没有帮助。编写自定义包装器怎么样:std::function
template <typename T> struct class_type;
template <typename C> struct class_type<bool(C::*)() const noexcept> { using type = C;};
template <auto F>
struct bool_member_to_functor {
using type = typename class_type<std::decay_t<decltype(F)>>::type;
bool operator()(const type& c) {
return (c.*F)();
}
};
int main (int argc, char** argv)
{
std::vector<std::vector<int>> vec;
std::any_of(vec.begin(),vec.end(),bool_member_to_functor<&std::vector<int>::empty>{});
}
这导致调用中的语法很好。直接传递成员函数是不可能的,但这已经尽可能接近了。但是,这里有点作弊。您需要更多的专业化来涵盖所有/非常量,而不是等变体。当成员函数重载时,情况会变得更糟。它不是你真正想写的东西。class_type
const
noexcept
结论:lambda 表达式是包装成员函数的轻量级、易读方式。我怀疑它能否变得更具可读性。
评论
std::bind_front(&std::vector<int>::empty)
在评论中,你说:
它没有什么明显的错误,除了如果确实有一种更短、(可以说)更具可读性的写作方式,
因此,您真正想要的似乎是使用命名函数(一种很好的编程实践)。我们可以这样做,并且仍然使用 lambda。只需创建一个命名的 lambda。
#include <algorithm>
auto isEmpty = [](std::vector<int> const& v) {return v.empty(); /* or std::empty(v) */};
int main()
{
std::vector<std::vector<int>> vec;
auto f = std::any_of(std::begin(vec), std::end(vec), isEmpty);
}
问题在于 C++ 中灵活的重载规则以及缺乏重载集管理的语义。成员函数可以通过 CV 以及其对象的引用 (rvalue/lvalue) 限定进行重载:
struct foo{
void bar()const &;
void bar()&&
void bz();
};
现在表达式变得模棱两可:有一个重载,一个是 const lvalue 对象,另一个是 rvalue 可变对象。消除表达式歧义的唯一方法是:&foo::bar
- 显式强制转换:
static_cast<void (foo::*) const &()>(&foo::bar)
- 或者绑定到调用站点的对象,该对象只能包装为 lambda。
但是,如果函数没有重载,则可以通过获取其地址来创建函数指针:工作没有任何问题。&foo::bz
为了解决上述困难,由 Eric Neibler 开创的库引入了 Neibloid 模式。这个想法可以针对任何非成员函数实现。上面链接中提供的文章不是对您的问题的直接回答。但它是由 C++ 社区的一个非常活跃的成员编写的,它向你展示了这种语言面临的许多挑战和技术细节。<ranges>
评论
std
std::empty()