CppWeekly 08 constexpr

这个系列是从这篇博客开始的,主要是复现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命令输出汇编显示,每次编译出的随机数都是不同的。神奇!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值