C++ 中文周刊 第35期

reddit/hackernews/lobsters/meetingcpp摘抄一些c++动态

每周更新

周刊项目地址在线地址知乎专栏 腾讯云+社区

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


资讯

编译器信息最新动态推荐关注hellogcc公众号

OSDT Weekly 2021-10-27 第121期

标准会十月邮件列表

文章

#include <initializer_list>
#include <iostream>

int main() {
  for (using T = int; T e : {1, 2}) {
    std::cout << e; // prints 1,2
  }

  for (struct T { int x; int y; }; T e : {T{1,2}, T{3,4}}) {
    std::cout << "{" << e.x << ',' << e.y << '}'; // prints {1,2}{3,4}
  }
}

if里面啥花活都能整了属于是

godbolt体验

struct Widget {};

namespace std {  // Danger!
    template<>
    struct hash<Widget> {
        size_t operator()(const Widget&) const;
    };
}

//这样写,不要上面那种写法
struct Widget {};

template<>
struct std::hash<Widget> {
    size_t operator()(const Widget&) const;
};

怎么保证一个函数只被调用一次呢,这里有个点子,Destructive separation: move away and call Matt Godbolt and his talk at C++ On Sea 2020.

#include <iostream>

class CostlyResult{};

class MyClass {
public:
  // ...
  [[nodiscard]] CostlyResult getCostly() && {
    return {};
  }
private:
};

int main() {
  MyClass mc;
  auto r = mc.getCostly();
}

这样调用会报错,因为你不是move的不能调用

于是就可以这样调用

auto r = std::move(mc).getCostly();

从而保证了一次调用,和生命周期同步了

但是,你要是这样调用

auto r = std::move(mc).getCostly();
auto r2 = std::move(mc).getCostly();

也拦不住。不过后面有篇文章继续讨论了这个话题

void DoSomething(const Configuration& p)
{
   // ...
}
 
class ConfigurationBuilder
{
public:
   ConfigurationBuilder& SetName(string name)
   {
       m_data.name = move(name);
       return *this;
   }
 
   ConfigurationBuilder& SetFolderPath(path folderPath)
   {
       m_data.folderPath = move(folderPath);
       return *this;
   }
   // ...
 
   Configuration Build()
   {
      return m_data;
   }
private:
   Configuration m_data;
};
 
//...
 
auto conf = ConfigurationBuilder{}.
                  SetName("marco").
                  Build();
DoSomething(conf);

这段代码的问题在于,不能保证别人执行了Build这行代码,也不能保证所有代码都只执行一次,怎么做?加上类型判定 + move

完整代码在这里

首先,有个全局的标记数组,这个数组可以编译期算值

namespace utils
{
    template<typename... Pack>
    struct pack
    {
         template<typename T>
         static constexpr ssize_t index_of = []{ 
                constexpr array<bool, sizeof...(Pack)> bits ;
                const auto it = find(begin(bits), end(bits), true);
                return it != end(bits) ? distance(begin(bits), it) : -1;
         }();
          
         template<typename T>
         static constexpr bool has = []{ 
                return index_of<T> != -1;
         }();
    };
}

直接has判断这个类型对应的flag是不是标记了

然后,定义各种tag类型

namespace tags
{
    struct set_name_called{};
    struct set_folder_called{};
}
 
struct Configuration
{
    std::string name;
    std::filesystem::path folderPath;
};
 
template<typename... Tags>
class ConfigurationBuilder
{
public:
    ConfigurationBuilder<tags::set_name_called, Tags...> SetName(string name) &&
    {
       static_assert(utils::pack<Tags...>::template index_of<tags::set_name_called> == -1, "'SetName' has already been called!");
       m_data.name = move(name);
       return {move(m_data)};
    }
     
    ConfigurationBuilder<tags::set_folder_called, Tags...> SetFolderPath(path folderPath) &&
    {
       static_assert(utils::pack<Tags...>::template index_of<tags::set_folder_called> == -1, "'SetFolderPath' has already been called!");
       m_data.folderPath = move(folderPath);
       return {move(m_data)};
    }
 
    Configuration Build() &&
    {
        static_assert(utils::pack<Tags...>::template index_of<tags::set_name_called> != -1, "'SetName' is mandatory");
        static_assert(utils::pack<Tags...>::template index_of<tags::set_folder_called> != -1, "'SetFolderPath' is mandatory");
        return move(m_data);
    }
private:
    ConfigurationBuilder() = default;
 
    ConfigurationBuilder(Configuration c)
       : m_data(move(c))
    {
    }
     
    template<typename... K>
    friend class ConfigurationBuilder;
 
    friend ConfigurationBuilder<> BuildConfiguration();
 
    Configuration m_data;
};
 
ConfigurationBuilder<> BuildConfiguration(){ return{}; }

第一次调用,没问题,标记,第二次调用,不满足条件,static_assert报错

不过,这个措施,有点点复杂

然后tag,有各种分类,在用继承之类的扩展

设置github项目支持微软代码分析工具

作者看汇编发现原来printf是puts实现/替换的

一个向量化优化策略

if (x > y) {
  do_something();
} else {
  do_something_else();
}

优化成

if (x > y) {
  do_something();
}
if (x <= y) {
  do_something_else();
}

当 x y不是NaN就可以这样优化

-ffinite-math-only告诉编译器,没有NaN,大胆去优化,但是如果x y恰巧是NaN,那就完了

一个汇编例子 godbolt

float a[1024];
float b[1024];

void foo(void) {
  for (int i = 0; i < 1024; ++i) {
    if (b[i] > 42.0f) {
      a[i] = b[i] + 1.0f;
    } else {
      b[i] = a[i] + 1.0f;
    }
  }
}

如果开了优化,b[i] 恰巧是NaN,那就完了,哪个if都不走

怎么处理这种问题?没有优雅的办法,这样也许可以

feenableexcept(FE_OVERFLOW | FE_INVALID | FE_DIVBYZERO);

但不优雅。如果开启这个优化,务必了解你的代码会不会有NaN。能精细的控制优化的前提是扣掉某些场景。如果你的场景包含NaN,就别用这个优化

实现<=>也得用friend惯用法,和其他的比较操作符类似,不然可能会有找不到调用的问题

struct Good {
    friend auto operator<=>(const Good&, const Good&) = default;
};

struct Bad {
    auto operator<=>(const Bad&) const = default;
};

static_assert(std::totally_ordered<Good>);
static_assert(std::totally_ordered<Bad>);

static_assert(std::totally_ordered<std::reference_wrapper<Good>>);
static_assert(not std::totally_ordered<std::reference_wrapper<Bad>>); // !!

简单来说是的 deque的empty就要比size快

但是有些自己实现的empty可能不一定比size == 0快。实现可能有问题

讨论了一些场景的返回值是否会被优化掉,copy elision的生效场景

讨论NTTP(Non-Type Template Parameters) 的用处,比如

template <size_t Length>
struct fixed_string {
    char _chars[Length+1] = {}; // +1 for null terminator
};
template <size_t N>
fixed_string(const char (&arr)[N])
    -> fixed_string<N-1>;  // Drop the null terminator

实现ts里类型检查,类似

type foo = { first: string, last: string };

const o = { first: "Foo", last: "Oof", age: 30 };
const p = { first: "Bar", last: "Rab", age: 45 };
const q = { first: "Baz", last: "Zab", gender: "m" };

const main = <T extends foo>(o: T) => (p: T) => o.first + o.last

main(o) (p); // type checks
main(o) (q); // type error

基本想法

import Mitama.Data.Extensible.Record;
#include <iostream>
#include <format>

using namespace mitama::literals;
using namespace std::literals;

void print(mitama::has<"name"_, "age"_> auto person) {
    std::cout << std::format("name = {}, age = {}\n", person["name"_], person["age"_]);
}

int main() {
    using mitama::as;
    // declare record type
    using Person = mitama::record
                   < mitama::named<"name"_, std::string>
                   , mitama::named<"age"_,  int>
                   >;

    // make record
    Person john = Person{
        "name"_v = "John"s,
        "age"_v  = 42,
    };

    // access to rows
    john["name"_]; // "John"
    john["age"_];  // 42

    print(john); // OK

    auto tom = mitama::empty
             += as<"name"_>("Tom"s)
             ;

    print(tom); // ERROR: constraints not satisfied
}

考虑如何实现?代码在这里

基本上是UDL实现name_ age_ ,然后用fix_string装起来,然后再判断不同的fix_string类型

google实现c++上的borrow checker遇到的困难

写移植Renderer遇到的问题,文章很长。这方面我不太懂,这里标记个TODO,后面补充

还是讨论可变返回类型

#include <iostream>
#include <typeinfo>
#include <type_traits>

template <typename T, typename T2>
auto sum(T t, T2 t2) -> decltype(t + t2) {
    return t + t2;
}


int main() {

    std::cout << '\n';

    std::cout << typeid(sum(5.5, 5.5)).name() << '\n';     // double
    std::cout << typeid(sum(5.5, true)).name() << '\n';    // double
    std::cout << typeid(sum(true, 5.5)).name() << '\n';    // double
    std::cout << typeid(sum(true, false)).name() << '\n';  // int

    std::cout << '\n';

}

c++20

#include <iostream>
#include <typeinfo>
#include <type_traits>

template<typename T>
concept Arithmetic = std::is_arithmetic<T>::value;

Arithmetic auto sum(Arithmetic auto t, Arithmetic auto t2) {
    return t + t2;
}


int main() {

    std::cout << '\n';

    std::cout << typeid(sum(5.5, 5.5)).name() << '\n';     // double
    std::cout << typeid(sum(5.5, true)).name() << '\n';    // double
    std::cout << typeid(sum(true, 5.5)).name() << '\n';    // double
    std::cout << typeid(sum(true, false)).name() << '\n';  // int

    std::cout << '\n';

}

视频

在线评价别人的代码中的API设计是不是合理

教你用协程写个parser,代码在这里

项目


本文永久链接

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