C++ 中文周刊 第142期

周刊项目地址

公众号

qq群 手机qq点击进入

RSS https://github.com/wanghenshui/cppweeklynews/releases.atom

欢迎投稿,推荐或自荐文章/软件/资源等

提交 issue

文章大部分来自

https://discu.eu/weekly/candcpp/2023/49/

https://www.meetingcpp.com/blog/blogroll/items/Meeting-Cpp-weekly-Blogroll-408.html

本期文章由 黄亮Anthony 赞助


资讯

标准委员会动态/ide/编译器信息放在这里

编译器信息最新动态推荐关注hellogcc公众号 OSDT Weekly 2023-12-13 第232期

另外PLCT有rsicv竞赛,感兴趣的可以参加一下 rvspoc.org

boost发布1.84版本,c++03全部抛弃,windows只支持win10及以上

新增redis cobalt库之前讲过

另外asio移除了execution相关设计。。

PFR支持fieldname 反射,要求c++20 https://github.com/boostorg/pfr/pull/129/files

效果 https://godbolt.org/z/xbo7bos86

#include <https://raw.githubusercontent.com/denzor200/pfr/amalgamate_get_name/include/boost/pfr/gen/pfr.hpp>
#include <functional>
#include <cstdio>
#include <cstring>

struct Any {
    Any() {};
};

struct XClass {
    int member1;
    Any this_is_a_name; // not constexpr constructible
    std::reference_wrapper<char> c; // not default constructible
};

int main() {
    char buf[32] {0};
    constexpr auto first = boost::pfr::get_name<0, XClass>();
    memcpy(buf, first.data(), first.size());
    puts(buf);
    
    static_assert("member1"        == boost::pfr::get_name<0, XClass>());
    static_assert("this_is_a_name" == boost::pfr::get_name<1, XClass>());
    static_assert("c"              == boost::pfr::get_name<2, XClass>());
}

Unordered支持concurrent_flat_set以及并发visit

其他的没啥说的。自己看吧

https://www.boost.org/users/history/version_1_84_0.html

文章

代码在这里 https://github.com/cdacamar/fredbuf

手把手教你实现编辑器

在130期咱们就聊过,如果cacheline 64,设置align 128能降低影响。lemire给了一种简单的测试方法,拷贝数组

https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2023/12/12/cacheline.c

小于cacheline,带宽没啥区别,受cacheline影响了,大于cacheline,越大越快,理论上加入cacheline 64 拷贝128,应该获得翻倍的速度,但是实际上并不是

建议大家自己玩玩,测一下效果。在m1表现也不太一样,但是小于cacheline拷贝速度不变这个现象是不变的

140期的调度器实现有bug,问题代码

class Scheduler {

  std::priority_queue<job, std::vector<job>, Comperator> _prioTasks;

  public: 
    void emplace(int prio, std::coroutine_handle<> task) {
      _prioTasks.push(std::make_pair(prio, task));
    }
}
Task createTask(const std::string& name) {
  std::cout << name << " start\n";
  co_await std::suspend_always();
  for (int i = 0; i <= 3; ++i ) { 
    std::cout << name << " execute " << i << "\n";                  // (5)
    co_await std::suspend_always();
  }
  co_await std::suspend_always();
  std::cout << name << " finish\n";
}

scheduler1.emplace(0, createTask("TaskA").get_handle());

看出来哪里有问题没有?createtask 的name的生命周期问题

成员函数参数不能直接用this

struct Sample
{
    int increment;
    void add(int v = increment); // not allowed
    void notify_all(Sample* source = this); // not allowed
};

猥琐绕过

struct Sample
{
    int increment;

    void add(int v);
    void add() { add(increment); }

    void notify_all(Sample* source);
    void notify_all() { notify_all(this); }
};

Sample s;

s.add(2); // adds 2
s.add(); // adds s.increment

s.notify_all(); // uses source = s
s.notify_all(other); // uses source = other

随机浮点数

比如 这个实现 https://dotat.at/@/2023-06-23-random-double.html

double pcg64_random_double(pcg64_t *rng) {
    return (double)(pcg64_random(rng) >> 11) * 0x1.0p-53;
}

luajit是这样的

uint64_t lj_prng_u64d(PRNGState *rs) {
    uint64_t z, r = 0;
    TW223_STEP(rs, z, r)
    /* Returns a double bit pattern in the range 1.0 <= d < 2.0. */
    return (r & 0x000fffffffffffffull) | 0x3ff0000000000000ull;
}
/* Then to give d in [0, 1) range: */
U64double u;
double d;
u.u64 = lj_prng_u64d(rs);
d = u.d - 1.0;

lemire博士的golang版本

// toFloat64 -> [0,1)
func toFloat64(seed *uint64) float64 {
    x := splitmix64(seed)
    x &= 0x1fffffffffffff // %2**53
    return float64(x) / float64(0x1fffffffffffff)
}

原理是这个 https://www.zhihu.com/question/25037345/answer/29879012

01之间

double rand_between_zero_and_one() {
    double d;
    uint64_t x = rand_u64() >> 11; /* 53-bit uniform integer */
    uint64_t e = 1022;
    do {
      if (rand_u64() & 1) break; /* 1-bit uniform integer */
      e -= 1;
    } while (e > 1022-75);
    x = ((x + 1) >> 1) + (e << 52);
    memcpy(&d, &x, sizeof(d));
    return d;
}

优化

double rand_between_zero_and_one() {
    double d;
    uint64_t x = rand_u64();
    uint64_t e = __builtin_ctzll(x) - 11ull;
    if ((int64_t)e >= 0) e = __builtin_ctzll(rand_u64());
    x = (((x >> 11) + 1) >> 1) - ((e - 1011ull) << 52);
    memcpy(&d, &x, sizeof(d));
    return d;
}

主要是要懂浮点数格式以及如何恰当的均匀分布

contains

#include <string>
#include <iostream>

int main(){
    const std::string url = "https://isocpp.org";
    
    if (url.contains("https") && 
        url.contains(".org") && 
        url.contains("isocpp"))
        std::cout << "you're using the correct site!\n";
}

starts_with(), ends_with()

insert range

#include <iostream>
#include <iterator>
#include <string>
 
int main() {
    const auto source = {'l', 'i', 'b', '_'};
    std::string target{"__cpp_containers_ranges"};
 
    const auto pos = target.find("container");
    auto iter = std::next(target.begin(), pos);
 
#ifdef __cpp_lib_containers_ranges
    target.insert_range(iter, source);
#else
    target.insert(iter, source.begin(), source.end());
#endif
 
    std::cout << target;
}

spanstream

#include <iostream>
#include <sstream>
#include <spanstream> // << new headeer!

void* operator new(std::size_t sz){
    std::cout << "Allocating: " << sz << '\n';
    return std::malloc(sz);
}

int main() {
    std::cout << "start...\n";
    std::stringstream ss;
    ss << "one string that doesn't fit into SSO";
    ss << "another string that hopefully won't fit";

    std::cout << "spanstream:\n";
    char buffer[128] { 0 };
    std::span<char> spanBuffer(buffer);
    std::basic_spanstream<char> ss2(spanBuffer);
    ss2 << "one string that doesn't fit into SSO";
    ss2 << "another string that hopefully won't fit";

    std::cout << buffer;
}

想了解cpython的可以看看

介绍spanstream的, 直接贴代码了

#include <iostream>
#include <span>
#include <spanstream>
#include <cassert>

void printSpan(auto spanToPrint) {
    for (size_t i = 0; i < spanToPrint.size(); ++i) {
        std::cout << spanToPrint[i];
    }
}

void useSpanbuf() {
    std::array<char, 16> charArray;
    std::span<char, 16> charArraySpan(charArray);
    std::spanbuf buf;

    char c = 'a';
    for (size_t i = 0; i < 16; ++i) {
        charArraySpan[i] = c;
        ++c;
    }
    
    buf.span(charArraySpan);

    // we can easily print a span got from the buffer
    std::span bufview = buf.span();
    std::cout << "bufview: ";
    for (size_t i = 0; i < 16; ++i) {
        std::cout << bufview[i];
    }
    std::cout << '\n';
}

void useSpanstream() {
    std::array<char, 16> charArray;
    std::ospanstream oss(charArray);

    oss << "Fortytwo is " << 42;
    // copying the contents to a span
    std::string s{oss.span().data(),size_t(oss.span().size())};
    assert(s == "Fortytwo is 42");
}


int main() {
    useSpanbuf();
    useSpanstream();

    return 0;
}

Raymond chen环节。我直接贴连接了。window是我不懂

linux环节

shmem tmpfs 比较经典

linux 5.6引入的,有意思

视频

姚奕正qds推荐

把 reflection/injection, pattern matching, senders都说了一遍,可以算是一个完全的新c++

在指针上做计算风险高,这也是为啥要引入span stringview,不用char * 信息丢失太多

cppcon2023

cpponsea 2023

考虑一个场景,

std::variant<int, float> foo();

std::variant<int, float,double> bar(){
  auto v = foo();
  ...
}

显然涉及到从std::variant不同参数的转换,怎么写?

template<class From, class To>
constexpr To variant_cast(From&& from) {
  return std::visit([](auto&& a) constexpr noexcept(
      std::is_nothrow_constructible_v<
        To,
        std::in_place_type_t<std::remove_cvref_t<decltype(a)>>,
        decltype(a)
        >) -> To {
          return To(
            std::in_place_type<std::remove_cvref_t<decltype(a)>>,
            std::forward<decltype(a)>(a));
      },
      std::foward<From>(from)
    );
}

存在一个问题,就是从std::variant<int, float>std::variant<int, double> 没法转,太safe了,得放宽一点



template<class To>
struct visitor {
  template<class T>
  requires std::constructible_from<To,std::in_place_type_t<std::remove_cvref_t<T>>,T>
  constexpr To operator()(T&& t) const noexcept(
    std::is_nothrow_constructible_v<To,std::in_place_type_t<std::remove_cvref_t<T>>,T>){
        return To(
          std::in_place_type<std::remove_cvref_t<decltype(a)>>,
          std::forward<T>(t));
  }
  template<class T>
  constexpr To operator()(T&& t) const noexcept {
    std::unreachable();
  }

};

template<class To, class From>
constexpr To uncheck_variant_cast(From&& from) {
  return std::visit(visitor<To>{}, std::forward<From>(from));
}

其实就是舍弃内部值本来的类型,用From硬推,匹配不到就失败

问题来了,如果是std::variant<const int, float>怎么办?

别琢磨了,能做,不支持,去他妈的,你就非得这么写吗,No

有意思的项目

互动环节

上一期提到的有问题的代码


int median(std::vector<int>& v) {
   int mid = v.size() / 2;
   std::nth_element(v.begin(), v.begin() + mid, v.end());
   int result = v[mid];
   if (v.size() % 2 == 0) {
     std::nth_element(v.begin(), v.begin() + mid - 1, v.end());
     result = (v[mid] + v[mid-1])/2;  
     // result = (result + v[mid-1]) /2;
   }
   return result;
}

周星星指出,完全可以第二个std::nth_element改成 std::max_element(begin,begin+mid)

确实是一个思路,或者更极端一点,偶数也不特殊处理就好了

huring指出这个问题在macbook m1上不复现我的提供的 10,10,29,18,10,10,10,10,13,32无法复现

只能说和平台也有点关系,巧合而已

突击提问:如何实现nth_element?最近面试刚被问到,我没上来优化的部分。已经自罚十个深蹲了

看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持! 觉得写的不错那就给点吧, 在线乞讨 微信转账