这篇写个平时易被忽略的小知识点,一元 + 操作符的使用技巧。

一般二元 + 操作符用得较多,只有一个操作数时,没人会多此一举地把 1 写成 +1

不过若是操作数为整数或无作用域枚举类型,一元 + 操作符会执行 Integral promotion,此时会发生隐式转换。例如:

// unscoped enumeration
enum Enum : unsigned int {
    enum_val_a,
    enum_val_b,
    enum_val_c
};

int main() {

    bool b = true;
    +b; // int

    +enum_val_b; // unsigned int

    char c = 'c';
    +c; // int

    unsigned short s = 10;
    +s; // int

    int array[10];
    +array; // int*
}

若是你使用的 C++ 标准不支持 std::to_underlying,你可能得使用以下语句来达到同样目的:

static_cast<std::underlying_type_t<Enum>>(enum_val_b);

这种写法太过繁琐,而以一元 + 操作符则可以非常简单地完成这种转换,当然前提须是 underlying 类型固定。

对于一些奇怪的类型,比如 std::uint8_t,它的类型是什么呢?顾名思义应该是 8-bit 的 Unsigned integer,然而实际上它是 unsigned chartypedef。那么在输出的时候就会遇到一些问题:

std::uint8_t u = 0x45;
std::cout << u; // E

最终输出将是 E,并不是一个无符号整数,你需要使用强制转换才能得到想要的输出。而借助一元 + 操作符,则可以非常简单地达到预期。

std::uint8_t u = 0x45;
std::cout << +u; // 69

另外,一元 + 操作符也支持指针类型的操作数,所以它也可以隐式地把 Lambda 转换为函数指针。例如:

auto fp = +[]{};
static_assert(std::is_same_v<decltype(fp), void (*)()>);

如果没有 +,那 fp 只是一个 closure 类型,断言出错。

另一个用法是在 Concepts 中,比如你想判断某类型当中是否存在某变量,可能会这样写:

template <typename T>
concept HasValue = requires(T t) {
    { T::num } -> std::integral;
};

struct S {
    int num;
};

// false
static_assert(HasValue<S>);

没能达到预期是因为 T::num 是个 value,而非 type。一种做法是采用 std::is_integral

template <typename T>
concept HasValue = requires(T t) {
    std::is_integral_v<decltype(T::num)>;
};

// true
static_assert(HasValue<S>);

这种做法就将 T::num 变成了 type,同理也可以这样做:

template <typename T>
concept HasValue = requires(T t) {
    decltype(T::num){};
};

约束必须是表达式,是以无法只写类型。更简单的话可以这样写:

template <typename T>
concept HasValue = requires(T t) {
    T::num++;
};

因为自增运算符也可以构成表达式,那么最简单的做法就是采用一元 + 操作符。

template <typename T>
concept HasValue = requires(T t) {
    +T::num;
};

那么有没有办法可以禁止 Integral promotion 呢?Concepts 便有此妙用。看下面这个例子:

uint8_t bad_foo(uint8_t a, uint8_t b) {
    return a + b; // implicit conversion
}

std::same_as<uint8_t> auto
good_foo(uint8_t a, uint8_t b) {
    return a + b; // Compile error!
}

对于 bad_foo()return a + b 在不经意间发生了 Integral promotion,它其实相当于return uint8_t((int)a + (int)b)。这种隐式转换的结果可能并不如人所愿,Concepts 相当于给返回值声明了 explict,从而避免错误。当你明确不需要返回值隐式转换的时候,可以借助这种方式。

活用这些小技巧,不仅可以简化代码,还能增加程序安全性。

Leave a Reply

Your email address will not be published. Required fields are marked *

You can use the Markdown in the comment form.