这个系列是从这篇博客开始的,主要是复现Jason Turner的“C++ Weekly With Jason Turner”视频中的代码。
041 constexpr
Jason 在这期和后面几期讨论了constexpr的一些用法,非常有意思。把部分简单的运算从运行时移动到了编译时,可以提高运行效率。但是我还没有真正找到必须要这样做的的实际样例,主要是我写的代码,瓶颈都不在这种地方(瓶颈多了去了, 呵呵)。
C++17 中constexpr
可以使用std::array
和lambda函数了。这里搬运Jason的代码,并做了一丁点简单调整,如下所示。代码中生成了一个std::array
,里面保存有16种颜色配置,需要生成一个新的array,内保存了上述16种颜色按照luma(可理解为亮度)从小到大排序的结果。所有运算均在编译时完成。
这是一个很好的例子解释了我们在编写c++代码时,我们是在设法和compiler沟通。compiler能做什么,做了什么,我们基本说的不算的,呵呵。
#include <algorithm>
#include <array>
#include <cstdint>
#include <iostream>
struct Color {
std::uint8_t num;
std::uint8_t r;
std::uint8_t g;
std::uint8_t b;
double luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
template < typename Colors >
constexpr auto nearest_color(
const Colors& colors,
const std::uint8_t r,
const std::uint8_t g,
const std::uint8_t b ) {
return *std::min_element(
begin(colors), end(colors),
[r, g, b](const auto& lhs, const auto& rhs){
const auto square = [](const auto& t){ return t*t; };
return ( square( lhs.r - r ) + square( lhs.g - g ) + square( lhs.b - b ) )
< ( square( rhs.r - r ) + square( rhs.g - g ) + square( rhs.b - b ) );
} );
}
template < std::size_t N >
constexpr auto sort_by_luma( const std::array< Color, N >& colors ) {
auto retVal = colors;
const auto arrayHead = &std::get<0>(retVal);
const auto end = arrayHead + colors.size();
for ( std::size_t i = 0; i < colors.size(); ++i ) {
const auto begin = arrayHead + i;
auto minElem = std::min_element( begin, end,
[](const auto& lhs, const auto& rhs){ return lhs.luma < rhs.luma; } );
const auto tmp = *minElem;
*minElem = *begin;
*begin = tmp;
}
return retVal;
}
int main() {
constexpr std::array< Color, 16 > colors {{
Color{0, 0x00, 0x00, 0x00},
Color{1, 0xFF, 0xFF, 0xFF},
Color{2, 0x88, 0x39, 0x32},
Color{3, 0x67, 0xB6, 0xBD},
Color{4, 0x8B, 0x3F, 0x96},
Color{5, 0x55, 0xA0, 0x49},
Color{6, 0x40, 0x31, 0x8D},
Color{7, 0xBF, 0xCE, 0x72},
Color{8, 0x8B, 0x54, 0x29},
Color{9, 0x57, 0x42, 0x00},
Color{10, 0xB8, 0x69, 0x62},
Color{11, 0x50, 0x50, 0x50},
Color{12, 0x78, 0x78, 0x78},
Color{13, 0x94, 0xE0, 0x89},
Color{14, 0x78, 0x69, 0xC4},
Color{15, 0x9F, 0x9F, 0x9F}
}};
static_assert( 12 == nearest_color( colors, 128, 128, 128 ).num );
static_assert( 0 == nearest_color( colors, 0, 0, 0 ).num );
constexpr auto sorted_colors = sort_by_luma( colors );
static_assert(sorted_colors[ 0].num == 0);
static_assert(sorted_colors[ 7].num == 14);
static_assert(sorted_colors[ 8].num == 12);
static_assert(sorted_colors[15].num == 1);
for( const auto& c : sorted_colors ) {
std::cout << static_cast<int>(c.num) << ": " << c.luma << '\n';
}
return sorted_colors[15].num;
}
若运行这段代码,终端输出的是sorted_colors
的内容,
0: 0
6: 58.8314
9: 65.6994
2: 73.29
11: 80
4: 85.439
8: 92.5884
14: 114.759
12: 120
10: 121.29
5: 137.774
15: 159
3: 165.71
7: 196.169
13: 201.561
1: 255
在complier explorer上,去掉所有和std::cout
相关的代码,编译优化为-O3
时,得到的汇编非常简单,就直接是一条mov
加一条ret
,就完事了,真神奇!见这里。
044 constexpr random number generator
我们甚至可以使用constexpr
在编译时生成随机数!先不要管这个是用来作什么的,但是听上去很酷。
#include <cstdint>
#include <limits>
constexpr auto seed() {
std::uint64_t shifted = 0;
for ( const auto c : __TIME__ ) {
shifted <<= 8;
shifted |= c;
}
return shifted;
}
struct PCG {
struct pcg_32_random_t {
std::uint64_t state = 0;
std::uint64_t inc = seed();
};
pcg_32_random_t rng;
typedef std::uint32_t ResultType_T;
constexpr ResultType_T operator()() {
return pcg_32_random_r();
}
static ResultType_T constexpr min() {
return std::numeric_limits<ResultType_T>::min();
}
static ResultType_T constexpr max() {
return std::numeric_limits<ResultType_T>::max();
}
private:
constexpr ResultType_T pcg_32_random_r() {
std::uint64_t oldState = rng.state;
rng.state = oldState * 6364136223846793005ULL + ( rng.inc|1 );
std::uint32_t xorshifted = ((oldState >> 18u) ^ oldState) >> 27u;
std::uint32_t rot = oldState >> 59u;
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}
};
constexpr auto get_random(int x) {
PCG pcg;
while ( x > 0 ) {
pcg();
--x;
}
return pcg();
}
int main() {
constexpr auto r = get_random(10);
return r;
}
上述代码每次编译,main()
的返回值都是一个随机的常量(基本随机,seed是通过时间生成的)。但是我在complier explorer上测试时,感觉并seed不一定是随时间变化的。但是在本地通过objdump -d
命令输出汇编显示,每次编译出的随机数都是不同的。神奇!