layout | title |
---|---|
post |
第35期 |
从reddit/hackernews/lobsters/meetingcpp摘抄一些c++动态
每周更新
欢迎投稿,推荐或自荐文章/软件/资源等,请提交 issue
#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里面啥花活都能整了属于是
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 {{ is_same<T, Pack>::value... }};
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,代码在这里
- Yet Another C/C++ Package Manager 又一个包管理,基于git和cmake的。(那我为什么不直接用cmake的fetchcontent)
- static_vector 用vector实现了static_vector,有点意思