从reddit/hackernews/lobsters/meetingcpp摘抄一些c++动态
每周更新
周刊项目地址|在线地址 |知乎专栏 | 腾讯云+社区 |
欢迎投稿,推荐或自荐文章/软件/资源等,请提交 issue
cppcon2021开完了,陆续把视频发出来,有几个更几个
meetingcpp这周也开始了,也是一周,所以这个十一月视频多的要死看不过来
想直接看的直接往下拉到视频环节
还是concept,复读几遍就记住了,这里在啰嗦一下
#include <concepts>
#include <iostream>
template<std::integral T>
void overloaded(T a)
{
std::cout << "Integral overload called with " << a << std::endl;
}
template<std::integral T> requires (sizeof(T) == 2)
void overloaded(T a)
{
std::cout << "Short overload called with " << a << std::endl;
}
int main()
{
int a{10};
short b{20};
overloaded(a);
overloaded(b);
}
你学会了吗~
又一个range教程,复读几遍读者就记住了,这里在啰嗦一下
替代stl
std::vector<int> dt = {1, 4, 2, 3};
std::ranges::sort(dt);
映射
struct Account {
std::string owner;
double value();
double base();
};std::vector<Account> acc = get_accounts();
// member
std::ranges::sort(acc,{},&Account::owner);
// member function
std::ranges::sort(acc,{},&Account::value);
// lambda
std::ranges::sort(acc,{},[](const auto& a) {
return a.value()+a.base();
});
view
namespace rv = std::ranges::views;
std::vector<int> dt = {1, 2, 3, 4, 5, 6, 7};
for (int v : rv::reverse(rv::take(rv::reverse(dt),3))) {
std::cout << v << ", ";
}
std::cout << "\n";
你学会了吗~
python有GIL大锁,最近社区突然有个牛人实现了一个去掉GIL的python,性能有提升,单核9%
这篇文章介绍一些相关的信息
- nogil版本从3.9拉出来的,现在已经3.11了,一时半会用不上,代码可能需要很久
- 3.11也有很多性能优化的提升,nogil版本比3.9快 ,但没有3.11快 (单核) 更细致的比较数据暂时没有
- 使用mimalloc替换内部pymalloc mimalloc在大量小对象场景上有很好的提升效果,且mimalloc本身的设计,竞争轻量
- 使用biased reference counting 降低引用计数的开销 论文在这
- 对象和自身的线程绑定
- 本地线程访问对象无需原子访问
- 其他线程访问还有有引用计数的增减的
- 一些对象就不计数了; 比如None, True, False, 内部字符串,小数字等等, PyTypeObjects for built-in types
- 一些对象就推迟计算引用计数了,module 代码块这些,可能和程序的生命周期相同
这里面mimalloc带来的提升很有效果,且mimalloc也有合作帮助优化cpython,这个mimalloc值得研究研究
另外,微软又出了个snmalloc,也有一些mimalloc的设计,也值得研究研究
手把手教你用goto
多层break场景
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 3; k++) {
printf("%d, %d, %d\n", i, j, k);
goto continue_i; // Now continuing the i loop!!
}
}
continue_i: ;
}
这种场景下,break continue都无法替代 (或者别这么写代码)
错误处理场景
for(...) {
for (...) {
while (...) {
do {
if (some_error_condition)
goto bail;
} while(...);
}
}
}
bail:
还有一种, 逐层清理 (c++有RAII处理这个)
if (init_system_1() == -1)
goto shutdown;
if (init_system_2() == -1)
goto shutdown_1;
if (init_system_3() == -1)
goto shutdown_2;
if (init_system_4() == -1)
goto shutdown_3;
do_main_thing(); // Run our program
shutdown_system4();
shutdown_3:
shutdown_system3();
shutdown_2:
shutdown_system2();
shutdown_1:
shutdown_system1();
shutdown:
print("All subsystems shut down.\n");
retry循环
retry:
byte_count = read(0, buf, sizeof(buf) - 1); // Unix read() syscall
if (byte_count == -1) { // An error occurred...
if (errno == EINTR) { // But it was just interrupted
printf("Restarting...\n");
goto retry;
}
while也可以
坑爹场景,goto和局部变量处理,局部变量生命周期要在goto的lable后可见
template<class, std::size_t> concept Any = true;
constexpr auto last1 = [](auto... args) {
return [&]<std::size_t... Ns>(std::index_sequence<Ns...>) {
return [](Any<Ns> auto..., auto last) {
return last;
}(args...);
}
(std::make_index_sequence<sizeof...(args) - 1>{});
};
auto last2 = [](auto... args) {
return (args, ...);
};
static_assert(1 == last1(1));
static_assert(2 == last1(1, 2));
static_assert(3 == last1(1, 2, 3));
static_assert(1 == last2(1));
static_assert(2 == last2(1, 2));
static_assert(3 == last2(1, 2, 3))
这个last2有点意思,不是新的东西 c++17也能编的过
吐槽coroutine难用,比如不是零开销抽象,api难用太低层之类的
一种代码api风格转换,套上观察者模式,代码在这里,可以看看
一个测试,malloc并没有返回报错而是直接崩溃了?
#include <stdio.h>
#include <stdlib.h>
int main() {
size_t large = 1099511627776;
char *buffer = (char *)malloc(large);
if (buffer == NULL) {
printf("error!\n");
return EXIT_FAILURE;
}
printf("Memory allocated\n");
for (size_t i = 0; i < large; i += 4096) {
buffer[i] = 0;
}
free(buffer);
return EXIT_SUCCESS;
}
分配的内存是虚拟内存,不是物理内存,直接访问,访问出segfault了,真奇怪
process | memory (virtual) | memory (real) |
---|---|---|
qemu | 3.94 GB | 32 MB |
safari | 3.7 GB | 180 MB |
auto可以用concept来修饰,从而实现限制auto。想要让auto在指定的concept下面auto
讲设计原理的。。。
这个之前说过,就是c++引入模式匹配,引入is as关键字
void f(auto const& x) {
inspect (x) {
i as int => std::cout << "int " << i;
[_,y] is [0,even] => std::cout << "point on y-axis and even y " << y;
[a,b] is [int,int] => std::cout << "2-int tuple " << a << " " << b;
s as std::string => std::cout << "string \"" + s + "\"";
is _ => std::cout << "((no matching value))";
}
}
int main() {
f(42);
f(std::pair{0, 2});
f(std::tuple{1, 2});
f("str");
struct {} foo;
f(foo);
}
和std::find没关系啊,这个就是讨论语义的,条件,限制之类的,Sean Paren大哥讲故事,还讲了几个冷笑话
没什么意思,讲语义的。给我听困了
单片机越来越廉价普及,应该大力发挥c++在其中的作用。然后说了一大堆概念。怎么都这么抽象
之前也说过这个思路,fixed_string + UDL
#include <algorithm>
#include <any>
#include <experimental/iterator>
#include <iostream>
#include <iterator>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
template <auto Size>
struct fixed_string {
char data[Size + 1]{};
static constexpr auto size = Size;
constexpr explicit(false) fixed_string(char const* str) {
std::copy_n(str, Size + 1, data);
}
constexpr explicit(false) operator std::string_view() const {
return {data, Size};
}
};
template <auto Size>
fixed_string(char const (&)[Size]) -> fixed_string<Size - 1>;
using std::literals::string_view_literals::operator""sv;
static_assert(""sv == fixed_string(""));
static_assert("name"sv == fixed_string("name"));
template <fixed_string Name, class TValue>
struct arg {
static constexpr auto name = Name;
TValue value{};
template <class T>
constexpr auto operator=(const T& t) {
return arg<Name, T>{.value = t};
}
};
namespace detail {
template <class TDefault, fixed_string, template <fixed_string, class> class>
auto map_lookup(...) -> TDefault;
template <class, fixed_string TKey, template <fixed_string, class> class TArg,
class TValue>
auto map_lookup(TArg<TKey, TValue>*) -> TArg<TKey, TValue>;
template <class TDefault, class, template <class, class> class>
auto map_lookup(...) -> TDefault;
template <class, class TKey, template <class, class> class TArg, class TValue>
auto map_lookup(TArg<TKey, TValue>*) -> TArg<TKey, TValue>;
} // namespace detail
template <class T, fixed_string TKey, class TDefault,
template <fixed_string, class> class TArg>
using map_lookup = decltype(detail::map_lookup<TDefault, TKey, TArg>(
static_cast<T*>(nullptr)));
template <class... Ts>
struct inherit : Ts... {};
static_assert(std::is_same_v<
void, map_lookup<inherit<arg<"price", double>, arg<"size", int>>,
"unknown", void, arg>>);
static_assert(
std::is_same_v<arg<"price", double>,
map_lookup<inherit<arg<"price", double>, arg<"size", int>>,
"price", void, arg>>);
static_assert(
std::is_same_v<arg<"size", int>,
map_lookup<inherit<arg<"price", double>, arg<"size", int>>,
"size", void, arg>>);
struct any : std::any {
any() = default;
template <class T>
explicit(false) any(const T& a)
: std::any{a},
print{[](std::ostream& os, const std::any& a) -> std::ostream& {
if constexpr (requires { os << std::any_cast<T>(a); }) {
os << std::any_cast<T>(a);
} else if constexpr (requires {
std::begin(std::any_cast<T>(a));
std::end(std::any_cast<T>(a));
}) {
auto obj = std::any_cast<T>(a);
std::copy(std::begin(obj), std::end(obj),
std::experimental::make_ostream_joiner(os, ','));
} else {
os << a.type().name();
}
return os;
}} {}
template <class T>
constexpr explicit(false) operator T() const {
return std::any_cast<T>(*this);
}
friend std::ostream& operator<<(std::ostream& os, const any& a) {
return a.print(os, a);
}
private:
std::ostream& (*print)(std::ostream&, const std::any&){};
};
template <fixed_string Name>
constexpr auto operator""_t() {
return arg<Name, any>{};
}
template <class T, class... TArgs>
decltype(void(T{std::declval<TArgs>()...}), std::true_type{})
test_is_braces_constructible(int);
template <class, class...>
std::false_type test_is_braces_constructible(...);
template <class T, class... TArgs>
using is_braces_constructible =
decltype(test_is_braces_constructible<T, TArgs...>(0));
struct any_type {
template <class T>
constexpr operator T(); // non explicit
};
template <class T>
constexpr auto to_tuple(T object) noexcept {
using type = std::decay_t<T>;
if constexpr (is_braces_constructible<type, any_type, any_type, any_type,
any_type, any_type, any_type, any_type,
any_type, any_type, any_type>{}) {
auto [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10] = std::forward<T>(object);
return std::tuple(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);
} else if constexpr (is_braces_constructible<type, any_type, any_type,
any_type, any_type, any_type,
any_type, any_type, any_type,
any_type>{}) {
auto [p1, p2, p3, p4, p5, p6, p7, p8, p9] = std::forward<T>(object);
return std::tuple(p1, p2, p3, p4, p5, p6, p7, p8, p9);
} else if constexpr (is_braces_constructible<
type, any_type, any_type, any_type, any_type,
any_type, any_type, any_type, any_type>{}) {
auto [p1, p2, p3, p4, p5, p6, p7, p8] = std::forward<T>(object);
return std::tuple(p1, p2, p3, p4, p5, p6, p7, p8);
} else if constexpr (is_braces_constructible<type, any_type, any_type,
any_type, any_type, any_type,
any_type, any_type>{}) {
auto [p1, p2, p3, p4, p5, p6, p7] = std::forward<T>(object);
return std::tuple(p1, p2, p3, p4, p5, p6, p7);
} else if constexpr (is_braces_constructible<type, any_type, any_type,
any_type, any_type, any_type,
any_type>{}) {
auto [p1, p2, p3, p4, p5, p6] = std::forward<T>(object);
return std::tuple(p1, p2, p3, p4, p5, p6);
} else if constexpr (is_braces_constructible<type, any_type, any_type,
any_type, any_type,
any_type>{}) {
auto [p1, p2, p3, p4, p5] = std::forward<T>(object);
return std::tuple(p1, p2, p3, p4, p5);
}
if constexpr (is_braces_constructible<type, any_type, any_type, any_type,
any_type>{}) {
auto [p1, p2, p3, p4] = std::forward<T>(object);
return std::tuple(p1, p2, p3, p4);
} else if constexpr (is_braces_constructible<type, any_type, any_type,
any_type>{}) {
auto [p1, p2, p3] = std::forward<T>(object);
return std::tuple(p1, p2, p3);
} else if constexpr (is_braces_constructible<type, any_type, any_type>{}) {
auto [p1, p2] = std::forward<T>(object);
return std::tuple(p1, p2);
} else if constexpr (is_braces_constructible<type, any_type>{}) {
auto [p1] = std::forward<T>(object);
return std::tuple(p1);
} else {
return std::tuple{};
}
}
// static_assert("name"sv == ("name"_t = 42).name);
// static_assert(42 == ("name"_t = 42).value);
template <class T>
[[nodiscard]] constexpr auto type_name() -> std::string_view {
#if defined(_MSC_VER) and not defined(__clang__)
return {&__FUNCSIG__[120], sizeof(__FUNCSIG__) - 128};
#elif defined(__clang_analyzer__)
return {&__PRETTY_FUNCTION__[57], sizeof(__PRETTY_FUNCTION__) - 59};
#elif defined(__clang__) and (__clang_major__ >= 12) and not defined(__APPLE__)
return {&__PRETTY_FUNCTION__[34], sizeof(__PRETTY_FUNCTION__) - 36};
#elif defined(__clang__)
return {&__PRETTY_FUNCTION__[70], sizeof(__PRETTY_FUNCTION__) - 72};
#elif defined(__GNUC__)
return {&__PRETTY_FUNCTION__[85], sizeof(__PRETTY_FUNCTION__) - 136};
#endif
}
namespace nt {
template <class B, class... Ts>
struct namedtuple : B, private Ts... {
static constexpr auto name_v = [] {
if constexpr (requires { B::name; }) {
return B::name;
} else {
return type_name<B>();
}
}();
static inline std::vector<std::string_view> names;
constexpr explicit(true) namedtuple(Ts... ts)
: B{[=] {
if constexpr (requires { B{ts.value...}; }) {
return B{ts.value...};
} else {
return B{};
}
}()},
Ts{ts}... {
names = {ts.name...};
}
template <class B_, class... Ts_>
auto& operator=(const namedtuple<B_, Ts_...>& other) {
names = {Ts_::name...};
static_cast<B&>(*this) = static_cast<const B&>(other);
return *this;
}
template <class T, class TArg = map_lookup<namedtuple, T::name, void, arg>>
constexpr const auto& operator[](const T) const
requires(not std::is_void_v<TArg>) {
return static_cast<const TArg&>(*this).value;
}
template <class T, class TArg = map_lookup<namedtuple, T::name, void, arg>>
constexpr auto& operator[](const T) requires(not std::is_void_v<TArg>) {
return static_cast<TArg&>(*this).value;
}
auto& assign(auto&&... ts) {
if constexpr ((requires {
ts.name;
ts.value;
} and
...)) {
((static_cast<decltype(ts)&>(*this) = ts), ...);
} else {
((static_cast<Ts&>(*this).value = ts), ...);
}
return *this;
}
template <std::size_t N>
auto& get() {
auto id_type = []<auto... Ns>(std::index_sequence<Ns...>) {
return inherit<
std::pair<std::integral_constant<std::size_t, Ns>, Ts>...>{};
}
(std::make_index_sequence<sizeof...(Ts)>{});
return static_cast<
typename decltype(detail::map_lookup<
void, std::integral_constant<std::size_t, N>,
std::pair>(&id_type))::second_type&>(*this);
}
template <std::size_t N>
const auto& get() const {
auto id_type = []<auto... Ns>(std::index_sequence<Ns...>) {
return inherit<
std::pair<std::integral_constant<std::size_t, Ns>, Ts>...>{};
}
(std::make_index_sequence<sizeof...(Ts)>{});
return static_cast<
const typename decltype(detail::map_lookup<
void, std::integral_constant<std::size_t, N>,
std::pair>(&id_type))::second_type&>(*this);
}
friend std::ostream& operator<<(std::ostream& os,
const namedtuple& nt) requires(sizeof...(Ts) >
0) {
os << std::string_view{name_v} << '{';
[&]<auto... Ns>(std::index_sequence<Ns...>) {
((os << (Ns ? "," : "") << std::string_view{Ts::name} << ':'
<< static_cast<const map_lookup<namedtuple, Ts::name, void, arg>&>(
nt)
.value),
...);
}
(std::make_index_sequence<sizeof...(Ts)>{});
os << '}';
return os;
}
friend std::ostream& operator<<(
std::ostream& os, const namedtuple& nt) requires(sizeof...(Ts) == 0) {
os << std::string_view{name_v} << '{';
auto t = to_tuple(static_cast<const B&>(nt));
[&]<auto... Ns>(std::index_sequence<Ns...>) {
((os << (Ns ? "," : "") << std::string_view{nt.names[Ns]} << ':'
<< std::get<Ns>(t)),
...);
}
(std::make_index_sequence<std::tuple_size_v<decltype(t)>>{});
os << '}';
return os;
}
};
template <class... Ts>
namedtuple(Ts...) -> namedtuple<void, Ts...>;
} // namespace nt
template <class T, class... Ts>
constexpr auto namedtuple(Ts... ts) {
return nt::namedtuple<T, Ts...>(ts...);
}
template <fixed_string Name, class... Ts>
constexpr auto namedtuple(Ts... ts) {
return nt::namedtuple<arg<Name, std::any>, Ts...>(ts...);
}
int main() {
// namedtuple in-place
const auto nt1 = namedtuple<"s">("price"_t = 42., "size"_t = 100);
std::cout << nt1["price"_t] << ',' << nt1["size"_t] << '\n' << nt1 << '\n';
// namedtuple in-place/struct
struct s {
double price;
int size;
};
const auto nt2 = namedtuple<s>("price"_t = 42., "size"_t = 100);
std::cout << nt2["price"_t] << ',' << nt2["size"_t] << '\n'
<< nt2.price << ',' << nt2.size << '\n'
<< nt2 << '\n';
// namedtuple struct
nt::namedtuple<s> nt3;
nt3 = namedtuple<s>("price"_t = 42., "size"_t = 100);
std::cout << nt3.price << ',' << nt3.size << '\n' << nt3 << '\n';
}
也直接贴代码
// Pretty-printer for `struct` layout and padding bytes
// by Vittorio Romeo (@supahvee1234) (https://vittorioromeo.info)
#include <boost/pfr.hpp>
#include <iostream>
#include <tuple>
#include <utility>
#include <type_traits>
#include <typeinfo>
#include <cmath>
#include <iomanip>
#include <cstring>
#include <array>
namespace detail
{
// ------------------------------------------------------------------------------
// Round `x` up to the nearest multiple of `mult`.
[[nodiscard]] constexpr std::size_t round_up(const std::size_t x,
const std::size_t mult) noexcept
{
return ((x + mult - 1) / mult) * mult;
}
// ------------------------------------------------------------------------------
// Recursively print the memory layout of `T` using identation `indent` and
// keeping track of the total occupied bytes in `used`.
template <typename T>
void print_layout_impl(const std::size_t indent, std::size_t& used)
{
// ------------------------------------------------------------------------------
// Utilities.
const char* const t_name = typeid(T).name();
const std::size_t line_length = std::strlen(t_name) + 32;
const auto print_indent = [&](const std::size_t spacing = 1)
{
for (std::size_t i = 0; i <= indent; ++i) { std::cout << ((i % 2 == 0) ? '|' : ' '); }
for (std::size_t i = 0; i < spacing; ++i) { std::cout << ' '; }
};
const auto print_line = [&]
{
print_indent(0);
for (std::size_t i = 0; i < line_length; ++i) { std::cout << '-'; }
std::cout << '\n';
};
// ------------------------------------------------------------------------------
// Tuple type of all data members of `T`, in order. Used to reflect on `T`.
using tuple_type = decltype(boost::pfr::structure_to_tuple(std::declval<T>()));
// ------------------------------------------------------------------------------
// We use a pointer to `std::tuple` to support non-default-constructible types.
// We need this inner lambda so that we can use `Ts...` as a pack.
[&]<typename... Ts>(std::tuple<Ts...>*)
{
// ------------------------------------------------------------------------------
// All alignments of the data members, with the alignment of `T` at the end.
constexpr std::array alignments{alignof(Ts)..., alignof(T)};
// ------------------------------------------------------------------------------
// Information printed in the header.
constexpr std::size_t sum_of_member_sizes = (sizeof(Ts) + ...);
constexpr std::size_t total_padding_bytes = (sizeof(T) - sum_of_member_sizes);
// ------------------------------------------------------------------------------
// Print the header.
print_line();
print_indent();
std::cout << t_name
<< " {size: " << sizeof(T) << " ("
<< sum_of_member_sizes << "# " << total_padding_bytes
<< "p), align: " << alignof(T) << "}\n";
print_line();
std::size_t type_idx = 0;
// -----------------------------------------------------------------------------
// Print padding in relation to a given alignment
const auto print_padding = [&](const std::size_t alignment)
{
const std::size_t padding = round_up(used, alignment) - used;
for (int i = 0; i < padding; ++i) { std::cout << 'p'; }
used += padding;
};
// -----------------------------------------------------------------------------
// Non-recursively print a fundamental/pointer/reference type
const auto print_fundamental = [&]<typename X>
{
print_indent();
std::cout << std::setw(2) << used << ": [";
print_padding(alignments[type_idx]);
for (std::size_t i = 0; i < sizeof(X); ++i) { std::cout << '#'; }
used += sizeof(X);
print_padding(alignments[type_idx + 1]);
std::cout << "] " << typeid(X).name() << '\n';
};
// -----------------------------------------------------------------------------
// Recursively print all data members
([&]
{
if constexpr(std::is_fundamental_v<Ts>
|| std::is_pointer_v<Ts>
|| std::is_reference_v<Ts>)
{
print_fundamental.template operator()<Ts>();
}
else
{
print_layout_impl<Ts>(indent + 2, used);
}
++type_idx;
}(), ...);
}(static_cast<tuple_type*>(nullptr));
print_line();
}
}
template <typename T>
void print_layout()
{
std::size_t used = 0;
detail::print_layout_impl<T>(0 /* indent */, used /* used */);
}
int main()
{
struct foo1
{
char *p; /* 8 bytes */
char c; /* 1 byte
char pad[7]; 7 bytes */
long x; /* 8 bytes */
};
print_layout<foo1>();
std::cout << '\n';
// ------------------------------------------------------------------------------
struct foo2
{
char c; /* 1 byte
char pad[7]; 7 bytes */
char* p; /* 8 bytes */
long x; /* 8 bytes */
};
print_layout<foo2>();
std::cout << '\n';
// ------------------------------------------------------------------------------
struct foo3
{
char* p; /* 8 bytes */
char c; /* 1 byte
char pad[7]; 7 bytes */
};
print_layout<foo3>();
std::cout << '\n';
// ------------------------------------------------------------------------------
struct foo4
{
short s; /* 2 bytes */
char c; /* 1 byte
char pad[1]; 1 byte */
};
print_layout<foo4>();
std::cout << '\n';
// ------------------------------------------------------------------------------
struct foo5
{
char c; /* 1 byte
char pad1[7]; 7 bytes */
struct foo5_inner
{
char* p; /* 8 bytes */
short x; /* 2 bytes
char pad2[6]; 6 bytes */
} inner;
};
print_layout<foo5::foo5_inner>();
std::cout << '\n';
print_layout<foo5>();
std::cout << '\n';
// ------------------------------------------------------------------------------
struct foo10
{
char c; /* 1 byte
char pad1[7]; 7 bytes */
foo10* p; /* 8 bytes */
short x; /* 2 bytes
char pad2[6]; 6 bytes */
};
print_layout<foo10>();
std::cout << '\n';
// ------------------------------------------------------------------------------
struct foo11
{
foo11* p; /* 8 bytes */
short x; /* 2 bytes */
char c; /* 1 byte
char pad[5]; 5 bytes */
};
print_layout<foo11>();
std::cout << '\n';
// ------------------------------------------------------------------------------
struct test0
{
int i;
char c;
float f;
};
print_layout<test0>();
std::cout << '\n';
// ------------------------------------------------------------------------------
struct test1
{
int i;
double d;
char c;
float f;
};
print_layout<test1>();
std::cout << '\n';
// ------------------------------------------------------------------------------
struct test2
{
void* p0;
test0 t0;
void* p1;
test1 t1;
void* p2;
};
print_layout<test2>();
std::cout << '\n';
// ------------------------------------------------------------------------------
struct test2_flat
{
void* p0;
int i0;
char c0;
float f0;
void* p1;
int i1;
double d0;
char c1;
float f1;
void* p2;
};
print_layout<test2_flat>();
std::cout << '\n';
}