这篇写个平时易被忽略的小知识点,一元 +
操作符的使用技巧。
一般二元 +
操作符用得较多,只有一个操作数时,没人会多此一举地把 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 char 的 typedef。那么在输出的时候就会遇到一些问题:
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
,从而避免错误。当你明确不需要返回值隐式转换的时候,可以借助这种方式。
活用这些小技巧,不仅可以简化代码,还能增加程序安全性。
你学会了吗?