跟我学C++中级篇——std::not_fn

一、std::not_fn定义和说明

std::not_fn这个模板函数非常有意思,在前面我们学习过wrapper(包装器),其实它就是通过封装一个包装器来实现返回值的非。它的基本定义如下:

c 复制代码
template< class F >
/* 未指定 */ not_fn( F&& f );(1)	(C++17 起)(C++20 起为 constexpr)
template< auto ConstFn >
constexpr /* 未指定 */ not_fn() noexcept;(2)	(C++26 起)

这里面的f参数是一个Callable对象。它的受限条件为:

1、std::decay_t 须为可调用 (Callable) 并支持移动构造 (MoveConstructible)

2、std::is_constructible_v<std::decay_t, F> 的结果必须为 true

那么,问题就来了,为什么要搞这么简单的一个东西呢?直接操作不更简单么?

二、应用

老生再次常谈一下,一切的技术的应用,跟场景的结合是无法独立出来的。也就是说,std::not_fn的应用,也不是放置四海皆优秀的。在实际的开发中,可能会遇到很多种情况,比如普通的函数,类成员函数,仿函数,甚至新标准中的Lambda表达式等等。

在处理这些情况的时候儿,可能直接操作返回一个非的结果很简单,也可能比较不简单。更有可能虽然简单但不好理解。而有的情况下开发者需要的不是一个简单的结果而是一个非的函数,凡此种种,都可能会有不同的需要。

一般来说这种在上层进行封装的应用,大多数情况下在底层应用比较多,比如本身就是库或框架。一如前面看到的STL中的元函数(如std::is_integral等),在业务层展现应用的机会很少,但一旦到底层的库编程,则应用大行其道。

三、混合应用

开发者经常会遇到这种问题,实现一个问题的一面(正面或反面)比较简单,而实现另外一面则相对复杂一些。这种简单和复杂不单指的实现上的简单,也包括代码阅读上的简单。比如实现一个返回True,需要一行代码,而返回False则需要几行代码。这种情况下,直接修改函数本身是没有问题的,但应用起来就等于是多实现了一次,而且如果需要将函数本身做为参数传递的话,这又是一个问题,很有可能在嵌套传参时导致行为的变形。

这时,使用std::not_fn直接生成一个新的非的函数,应用起来就会非常清晰明了。下面看一个简单的例程:

c 复制代码
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>

// 是否为偶数
bool is_even(int x) {
    return x % 2 == 0;
}

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    auto is_odd = std::not_fn(is_even);

    // 打印
    std::vector<int> odd_numbers;
    std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(odd_numbers), is_odd);

    std::cout << "Odd numbers: ";
    for (int num : odd_numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

其实在基础库的很类似的应用 中都可以应用到std::not_fn,在代码层级更容易理解。

四、特点

std::not_fn主要是为了替代前面的替代std::not1,std::not2这类非相关的处理。毕竟后两个依赖于std::unary_function 或 std::binary_function的实现。从设计角度看,这种应用方式首先就限定的灵活性及通用性或者理解为适配性不好。那么std::not_fn有什么特点呢:

1、优势

a)更高一层的抽象可以让开发者离开代码内部的实现,专注于逻辑的实现

b)适应性强,对所有的类函数(如普通函数、类内部成员函数、仿函数等等)都支持

c)c++20后支持constexpr,可在编译期进行优化

d)代码更直观,容易理解和维护

2、劣势

a)过深的嵌套可能逻辑的复杂化和提高后期维护的复杂度

b)应用范围受限,一般要求返回值的类型必须为可转化为bool类型的数据类型

c)在很简单的非应用时,std::not_fn并没有优势

所以开发者的头脑中始终要有根弦警惕着,技术要善用而非乱用!

五、例程

下面看一个cppreference的例程:

c 复制代码
#include <cassert>
#include <functional>

bool is_same(int a, int b) noexcept
{
    return a == b;
}

struct S
{
    int val;
    bool is_same(int arg) const noexcept { return val == arg; }
};

int main()
{
    // 用于自由函数:
    auto is_differ = std::not_fn(is_same);
    assert(is_differ(8, 8) == false); // 等价于:!is_same(8, 8) == false
    assert(is_differ(6, 9) == true); // 等价于:!is_same(8, 0) == true

    // 用于成员函数:
    auto member_differ = std::not_fn(&S::is_same);
    assert(member_differ(S{3}, 3) == false); // 等价于:S tmp{6}; !tmp.is_same(6) == false

    // 保持 noexcept 说明:
    static_assert(noexcept(is_differ) == noexcept(is_same));
    static_assert(noexcept(member_differ) == noexcept(&S::is_same));

    // 用于函数对象:
    auto same = [](int a, int b) { return a == b; };
    auto differ = std::not_fn(same);
    assert(differ(1, 2) == true); // 等价于:!same(1, 2) == true
    assert(differ(2, 2) == false); // 等价于:!same(2, 2) == false

#if __cpp_lib_not_fn >= 202306L
    auto is_differ_cpp26 = std::not_fn<is_same>();
    assert(is_differ_cpp26(8, 8) == false);
    assert(is_differ_cpp26(6, 9) == true);

    auto member_differ_cpp26 = std::not_fn<&S::is_same>();
    assert(member_differ_cpp26(S{3}, 3) == false);

    auto differ_cpp26 = std::not_fn<same>();
    static_assert(differ_cpp26(1, 2) == true);
    static_assert(differ_cpp26(2, 2) == false);
#endif
}

例程非常简单,其实可以理解为对函数指针F的一种非的反向控制。它可以提供更灵活的控制方式,而不必直接修改相关的代码。

六、总结

std::not_fn本身并没有什么难度。但只要认真想一下,其实就难明白,它其实就是让编程变得更灵活和更容易控制。尽最大可能的减少对程序的整体的影响或产生副作用。特别是对已经存在的老的代码的完善和更新的情况下,这种处理方式是一种非常合理和便捷的存在。

当把一个操作看成一个黑盒时,它们内外的交互就不存在了,那么无论对哪一方来说,这都是好事儿。简单永远是开发者追求的目标!

相关推荐
<但凡.13 分钟前
C++修炼:内存管理
c++·算法
ephemerals__1 小时前
【c++】异常处理
c++
菜_小_白1 小时前
mysql连接池
linux·c++·mysql
SunkingYang1 小时前
MFC中CString类型是如何怎么转std::string的
c++·mfc·cstring转string
梦醒沉醉1 小时前
C++和标准库速成(十)——类型别名、类型定义、类型推断和标准库简介
c++
wen__xvn1 小时前
每日一题力扣3248.矩阵中的蛇c++
c++·leetcode·矩阵
进击的jerk2 小时前
力扣45.跳跃游戏
开发语言·c++·算法·leetcode·游戏
rigidwill6662 小时前
LeetCode hot 100—颜色分类
数据结构·c++·算法·leetcode·职场和发展
__echooo2 小时前
【算法】力扣 713题:乘积小于 K 的子数组之深入思考
数据结构·c++·算法·leetcode·面试
珹洺3 小时前
C++从入门到实战(五)类和对象(第一部分)为什么有类,及怎么使用类,类域概念详解(附带图谱等更好对比理解)
java·c语言·开发语言·数据结构·c++·redis·缓存