std::function/bind 类似于 type-erasure 没有标准 C++ 库
std::function/bind like type-erasure without Standard C++ library
我正在基于 publish/subscribe 模式在 C++11 中开发一个简单的事件驱动应用程序。 类 有一个或多个 onWhateverEvent()
事件循环调用的方法(控制反转)。由于应用程序实际上是一个固件,其中代码大小很关键,灵活性不是高优先级,'subscribe' 部分是一个简单的 table,带有事件 ID 和关联的处理程序。
这是一个非常简化的想法代码:
#include <functional>
enum Events {
EV_TIMER_TICK,
EV_BUTTON_PRESSED
};
struct Button {
void onTick(int event) { /* publish EV_BUTTON_PRESSED */ }
};
struct Menu {
void onButtonPressed(int event) { /* publish EV_SOMETHING_ELSE */ }
};
Button button1;
Button button2;
Menu mainMenu;
std::pair<int, std::function<void(int)>> dispatchTable[] = {
{EV_TIMER_TICK, std::bind(&Button::onTick, &button1, std::placeholders::_1) },
{EV_TIMER_TICK, std::bind(&Button::onTick, &button2, std::placeholders::_1) },
{EV_BUTTON_PRESSED, std::bind(&Menu::onButtonPressed, &mainMenu, std::placeholders::_1) }
};
int main(void)
{
while(1) {
int event = EV_TIMER_TICK; // msgQueue.getEventBlocking();
for (auto& a : dispatchTable) {
if (event == a.first)
a.second(event);
}
}
}
这可以在桌面编译器上正常编译和运行,并且 std:function<void(int)>> fn = std::bind(&SomeClass::onSomething), &someInstance, std::placeholders::_1)
优雅地实现了类型擦除,因此事件调度 table 可以容纳不同 class 的处理程序,因此可以容纳不同的类型。
问题出在支持C++11的嵌入式编译器(AVR-GCC 4.8.3),但没有标准C++库:没有<functional>
header。我在想如何 re-create 仅使用编译器功能才能实现上述行为。我评估了几个选项,但每个选项都有反对意见(编译器或我):
用virtual void Handler::onEvent(int event)
方法创建接口,并从中派生出Button
和Menu
。 dispatch table 可以保存接口指针,其余的由虚方法调用完成。这是最简单的方法,但我不喜欢将事件处理程序方法的数量限制为每个 class(通过执行本地 if-else 事件调度)的想法,并且具有虚拟的开销每个事件的方法调用。
我的第二个思路还是包含虚方法调用,但是对Button
和Menu
class没有限制。这是一个基于 type-erasure 的虚拟方法调用,带有仿函数:
struct FunctBase {
virtual void operator()(int event) = 0;
};
template<typename T>
struct Funct : public FunctBase
{
T* pobj; //instance ptr
void (T::*pmfn)(int); //mem fun ptr
Funct(T* pobj_, void (T::*pmfn_)(int)) : pobj(pobj_), pmfn(pmfn_) {}
void operator()(int ev) override {
(pobj->*pmfn)(ev);
}
};
Funct
可以保存实例和方法指针,调度 table 可以由 FunctBase
指针构造。这种方式 table 与 function/bind 一样灵活:可以容纳任何 class(类型)和每个 class 的任意数量的处理程序。我唯一的问题是它仍然包含每个事件的 1 个虚拟方法调用,它只是移动到仿函数。
我的第三个想法是将方法指针转换为函数指针的简单 hack:
typedef void (*Pfn)(void*, int);
Pfn pfn1 = reinterpret_cast<Pfn>(&Button::onTick);
Pfn pfn2 = reinterpret_cast<Pfn>(&Menu::onButtonPressed);
据我所知,这是未定义的行为,确实会让编译器发出一个很大的警告。它基于 C++ 方法具有指向 this
的隐式第一个参数的假设。尽管如此,它仍然有效,它是轻量级的(没有虚拟调用),而且它很灵活。
所以我的问题是:是否有可能以干净的 C++ 方式执行选项 3 之类的操作?我知道有一种基于 void* 的 type-erasure 技术(与选项 2 中的虚拟方法调用相反),但我不知道如何实现它。查看带有 std::bind 的桌面版本也给我的印象是它将第一个隐式参数绑定为实例指针,但也许这只是语法。
您的第一个想法是典型的面向对象的问题解决方案。它非常好,但有点笨拙 - 不如 std::function
好用。你的第三个想法是未定义的行为。不不不不。
您的第二个想法 - 现在我们可以合作了!这接近 std::function
的实际实现方式。我们可以写一个 class 可以接受任何可以用 int
和 returns void
:
调用的对象
class IntFunc {
private:
struct placeholder {
virtual ~placeholder() = default;
virtual void call(int ) = 0;
};
template <typename F>
struct holder : placeholder {
holder(F f) : func(f) { }
void call(int i) override { func(i); }
F func;
};
// if you wrote your own unique_ptr, use it here
// otherwise, will have to add rule of 5 stuff
placeholder* p;
public:
template <typename F>
IntFunc(F f)
: placeholder(new holder<F>(f))
{ }
template <typename Cls>
IntFunc(Cls* instance, void (Cls::*method)(int )) {
auto lambda = [=](int i){ (instance->*method)(i); };
placeholder = new holder<decltype(lambda)>(lambda);
}
void operator()(int i) {
p->call(i);
}
};
有了它,您基本上就可以以一种可用的通用方式获得 std::function<void(int)>
。
现在 第 4 个想法可能只是将您的第 3 个想法扩展到可用的东西。实际使用函数指针:
using Pfn = void (*)(void*, int);
然后使用 lambdas 来做这样的事情:
Pfn buttonOnTick = [](void* ctxt, int i){
static_cast<Button*>(ctxt)->onTick(i);
};
但是你必须以某种方式坚持上下文 - 这会增加额外的工作。
可靠、高效、std::function<R(Args...)>
的替换并不难写。
由于我们是嵌入式的,所以我们希望避免分配内存。所以我会写一个small_task< Signature, size_t sz, size_t algn >
。它创建一个大小为 sz
且对齐方式为 algn
的缓冲区,用于存储已擦除的对象。
它还存储了一个移动器、一个破坏器和一个调用函数指针。这些指针可以在本地 small_task
(最大位置)内,也可以在手动 struct vtable { /*...*/ } const* table
.
内
template<class Sig, size_t sz, size_t algn>
struct small_task;
template<class R, class...Args, size_t sz, size_t algn>
struct small_task<R(Args...), sz, algn>{
struct vtable_t {
void(*mover)(void* src, void* dest);
void(*destroyer)(void*);
R(*invoke)(void const* t, Args&&...args);
template<class T>
static vtable_t const* get() {
static const vtable_t table = {
[](void* src, void*dest) {
new(dest) T(std::move(*static_cast<T*>(src)));
},
[](void* t){ static_cast<T*>(t)->~T(); },
[](void const* t, Args&&...args)->R {
return (*static_cast<T const*>(t))(std::forward<Args>(args)...);
}
};
return &table;
}
};
vtable_t const* table = nullptr;
std::aligned_storage_t<sz, algn> data;
template<class F,
class dF=std::decay_t<F>,
// don't use this ctor on own type:
std::enable_if_t<!std::is_same<dF, small_task>{}>* = nullptr,
// use this ctor only if the call is legal:
std::enable_if_t<std::is_convertible<
std::result_of_t<dF const&(Args...)>, R
>{}>* = nullptr
>
small_task( F&& f ):
table( vtable_t::template get<dF>() )
{
// a higher quality small_task would handle null function pointers
// and other "nullable" callables, and construct as a null small_task
static_assert( sizeof(dF) <= sz, "object too large" );
static_assert( alignof(dF) <= algn, "object too aligned" );
new(&data) dF(std::forward<F>(f));
}
// I find this overload to be useful, as it forces some
// functions to resolve their overloads nicely:
// small_task( R(*)(Args...) )
~small_task() {
if (table)
table->destroyer(&data);
}
small_task(small_task&& o):
table(o.table)
{
if (table)
table->mover(&o.data, &data);
}
small_task(){}
small_task& operator=(small_task&& o){
// this is a bit rude and not very exception safe
// you can do better:
this->~small_task();
new(this) small_task( std::move(o) );
return *this;
}
explicit operator bool()const{return table;}
R operator()(Args...args)const{
return table->invoke(&data, std::forward<Args>(args)...);
}
};
template<class Sig>
using task = small_task<Sig, sizeof(void*)*4, alignof(void*) >;
缺少的另一件事是高质量 void(Args...)
,它不关心传入的可调用对象是否具有 return 值。
以上任务支持移动,不支持复制。添加副本意味着存储的所有内容都必须是可复制的,并且需要 vtable 中的另一个函数(实现类似于 move
,除了 src
是 const
而不是 std::move
)。
使用了少量的 C++14,即 enable_if_t
和 decay_t
别名等。它们可以很容易地用 C++11 编写,或者替换为 typename std::enable_if<?>::type
.
老实说,bind
最好用 lambda 代替。我什至在非嵌入式系统上也不使用它。
另一个改进是教它如何处理 smaller/less 对齐的 small_task
,方法是存储它们的 vtable
指针而不是将其复制到 data
缓冲区,并将其包装在另一个 vtable
中。这将鼓励使用刚好足以解决您的问题集的 small_tasks
。
将成员函数转换为函数指针不仅是未定义的行为,而且函数的调用约定通常与成员函数不同。特别是,this
根据某些调用约定在特定寄存器中传递。
这样的差异可能很微妙,当您更改不相关的代码、编译器版本更改或其他任何原因时,就会突然出现。所以我会避免这种情况,除非你别无选择。
如前所述,该平台缺少库。上面的std
的每次使用都是很小的,所以我就写它们:
template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;
using size_t=decltype(sizeof(int));
移动
template<class T>
T&& move(T&t){return static_cast<T&&>(t);}
转发
template<class T>
struct remove_reference:tag<T>{};
template<class T>
struct remove_reference<T&>:tag<T>{};
template<class T>using remove_reference_t=type_t<remove_reference<T>>;
template<class T>
T&& forward( remove_reference_t<T>& t ) {
return static_cast<T&&>(t);
}
template<class T>
T&& forward( remove_reference_t<T>&& t ) {
return static_cast<T&&>(t);
}
衰退
template<class T>
struct remove_const:tag<T>{};
template<class T>
struct remove_const<T const>:tag<T>{};
template<class T>
struct remove_volatile:tag<T>{};
template<class T>
struct remove_volatile<T volatile>:tag<T>{};
template<class T>
struct remove_cv:remove_const<type_t<remove_volatile<T>>>{};
template<class T>
struct decay3:remove_cv<T>{};
template<class R, class...Args>
struct decay3<R(Args...)>:tag<R(*)(Args...)>{};
template<class T>
struct decay2:decay3<T>{};
template<class T, size_t N>
struct decay2<T[N]>:tag<T*>{};
template<class T>
struct decay:decay2<remove_reference_t<T>>{};
template<class T>
using decay_t=type_t<decay<T>>;
is_convertible
template<class T>
T declval(); // no implementation
template<class T, T t>
struct integral_constant{
static constexpr T value=t;
constexpr integral_constant() {};
constexpr operator T()const{ return value; }
constexpr T operator()()const{ return value; }
};
template<bool b>
using bool_t=integral_constant<bool, b>;
using true_type=bool_t<true>;
using false_type=bool_t<false>;
template<class...>struct voider:tag<void>{};
template<class...Ts>using void_t=type_t<voider<Ts...>>;
namespace details {
template<template<class...>class Z, class, class...Ts>
struct can_apply:false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;
namespace details {
template<class From, class To>
using try_convert = decltype( To{declval<From>()} );
}
template<class From, class To>
struct is_convertible : can_apply< details::try_convert, From, To > {};
template<>
struct is_convertible<void,void>:true_type{};
enable_if
template<bool, class=void>
struct enable_if {};
template<class T>
struct enable_if<true, T>:tag<T>{};
template<bool b, class T=void>
using enable_if_t=type_t<enable_if<b,T>>;
result_of
namespace details {
template<class F, class...Args>
using invoke_t = decltype( declval<F>()(declval<Args>()...) );
template<class Sig,class=void>
struct result_of {};
template<class F, class...Args>
struct result_of<F(Args...), void_t< invoke_t<F, Args...> > >:
tag< invoke_t<F, Args...> >
{};
}
template<class Sig>
using result_of = details::result_of<Sig>;
template<class Sig>
using result_of_t=type_t<result_of<Sig>>;
aligned_storage
template<size_t size, size_t align>
struct alignas(align) aligned_storage_t {
char buff[size];
};
is_same
template<class A, class B>
struct is_same:false_type{};
template<class A>
struct is_same<A,A>:true_type{};
live example,我需要每个 std
库模板大约十几行。
我会将此 "std library reimplementation" 放入 namespace notstd
以明确发生了什么。
如果可以,请使用将相同功能折叠在一起的链接器,例如 gold 链接器。模板元编程可能会导致二进制膨胀,而没有可靠的链接器来剥离它。
在尝试手动编写所有 STL 内容之前,我尝试使用编译器本身已经拥有的 STL。因为您使用的大多数 STL 代码只是 header,所以我只是简单地包含它并做一些小改动来编译它们。事实上,id 花了 10 分钟准备 link!
我用的是avr-gcc-5.2.0版本,没有任何问题。我没有旧安装,我相信在几分钟内安装实际版本比修复旧版本的问题更容易。
在为 avr 编译你的示例代码后,我得到 link 个错误:
build-check-std-a520-nomemdbg-os-dynamic-noncov/main.o: In function `std::__throw_bad_function_call()':
/home/krud/own_components/avr_stl/avr_stl009/testing/main.cpp:42: undefined reference to `operator delete(void*, unsigned int)'
/home/krud/own_components/avr_stl/avr_stl009/testing/main.cpp:42: undefined reference to `operator delete(void*, unsigned int)'
collect2: error: ld returned 1 exit status
只需编写自己的 __throw_bad_function_call
即可摆脱 link 问题。
对我来说,编写自己的 STL 实现真的没有意义。这里我简单地使用了来自编译器安装(gcc 5.2.0)的headers。
我正在基于 publish/subscribe 模式在 C++11 中开发一个简单的事件驱动应用程序。 类 有一个或多个 onWhateverEvent()
事件循环调用的方法(控制反转)。由于应用程序实际上是一个固件,其中代码大小很关键,灵活性不是高优先级,'subscribe' 部分是一个简单的 table,带有事件 ID 和关联的处理程序。
这是一个非常简化的想法代码:
#include <functional>
enum Events {
EV_TIMER_TICK,
EV_BUTTON_PRESSED
};
struct Button {
void onTick(int event) { /* publish EV_BUTTON_PRESSED */ }
};
struct Menu {
void onButtonPressed(int event) { /* publish EV_SOMETHING_ELSE */ }
};
Button button1;
Button button2;
Menu mainMenu;
std::pair<int, std::function<void(int)>> dispatchTable[] = {
{EV_TIMER_TICK, std::bind(&Button::onTick, &button1, std::placeholders::_1) },
{EV_TIMER_TICK, std::bind(&Button::onTick, &button2, std::placeholders::_1) },
{EV_BUTTON_PRESSED, std::bind(&Menu::onButtonPressed, &mainMenu, std::placeholders::_1) }
};
int main(void)
{
while(1) {
int event = EV_TIMER_TICK; // msgQueue.getEventBlocking();
for (auto& a : dispatchTable) {
if (event == a.first)
a.second(event);
}
}
}
这可以在桌面编译器上正常编译和运行,并且 std:function<void(int)>> fn = std::bind(&SomeClass::onSomething), &someInstance, std::placeholders::_1)
优雅地实现了类型擦除,因此事件调度 table 可以容纳不同 class 的处理程序,因此可以容纳不同的类型。
问题出在支持C++11的嵌入式编译器(AVR-GCC 4.8.3),但没有标准C++库:没有<functional>
header。我在想如何 re-create 仅使用编译器功能才能实现上述行为。我评估了几个选项,但每个选项都有反对意见(编译器或我):
用
virtual void Handler::onEvent(int event)
方法创建接口,并从中派生出Button
和Menu
。 dispatch table 可以保存接口指针,其余的由虚方法调用完成。这是最简单的方法,但我不喜欢将事件处理程序方法的数量限制为每个 class(通过执行本地 if-else 事件调度)的想法,并且具有虚拟的开销每个事件的方法调用。我的第二个思路还是包含虚方法调用,但是对
Button
和Menu
class没有限制。这是一个基于 type-erasure 的虚拟方法调用,带有仿函数:struct FunctBase { virtual void operator()(int event) = 0; }; template<typename T> struct Funct : public FunctBase { T* pobj; //instance ptr void (T::*pmfn)(int); //mem fun ptr Funct(T* pobj_, void (T::*pmfn_)(int)) : pobj(pobj_), pmfn(pmfn_) {} void operator()(int ev) override { (pobj->*pmfn)(ev); } };
Funct
可以保存实例和方法指针,调度 table 可以由FunctBase
指针构造。这种方式 table 与 function/bind 一样灵活:可以容纳任何 class(类型)和每个 class 的任意数量的处理程序。我唯一的问题是它仍然包含每个事件的 1 个虚拟方法调用,它只是移动到仿函数。我的第三个想法是将方法指针转换为函数指针的简单 hack:
typedef void (*Pfn)(void*, int); Pfn pfn1 = reinterpret_cast<Pfn>(&Button::onTick); Pfn pfn2 = reinterpret_cast<Pfn>(&Menu::onButtonPressed);
据我所知,这是未定义的行为,确实会让编译器发出一个很大的警告。它基于 C++ 方法具有指向
this
的隐式第一个参数的假设。尽管如此,它仍然有效,它是轻量级的(没有虚拟调用),而且它很灵活。
所以我的问题是:是否有可能以干净的 C++ 方式执行选项 3 之类的操作?我知道有一种基于 void* 的 type-erasure 技术(与选项 2 中的虚拟方法调用相反),但我不知道如何实现它。查看带有 std::bind 的桌面版本也给我的印象是它将第一个隐式参数绑定为实例指针,但也许这只是语法。
您的第一个想法是典型的面向对象的问题解决方案。它非常好,但有点笨拙 - 不如 std::function
好用。你的第三个想法是未定义的行为。不不不不。
您的第二个想法 - 现在我们可以合作了!这接近 std::function
的实际实现方式。我们可以写一个 class 可以接受任何可以用 int
和 returns void
:
class IntFunc {
private:
struct placeholder {
virtual ~placeholder() = default;
virtual void call(int ) = 0;
};
template <typename F>
struct holder : placeholder {
holder(F f) : func(f) { }
void call(int i) override { func(i); }
F func;
};
// if you wrote your own unique_ptr, use it here
// otherwise, will have to add rule of 5 stuff
placeholder* p;
public:
template <typename F>
IntFunc(F f)
: placeholder(new holder<F>(f))
{ }
template <typename Cls>
IntFunc(Cls* instance, void (Cls::*method)(int )) {
auto lambda = [=](int i){ (instance->*method)(i); };
placeholder = new holder<decltype(lambda)>(lambda);
}
void operator()(int i) {
p->call(i);
}
};
有了它,您基本上就可以以一种可用的通用方式获得 std::function<void(int)>
。
现在 第 4 个想法可能只是将您的第 3 个想法扩展到可用的东西。实际使用函数指针:
using Pfn = void (*)(void*, int);
然后使用 lambdas 来做这样的事情:
Pfn buttonOnTick = [](void* ctxt, int i){
static_cast<Button*>(ctxt)->onTick(i);
};
但是你必须以某种方式坚持上下文 - 这会增加额外的工作。
可靠、高效、std::function<R(Args...)>
的替换并不难写。
由于我们是嵌入式的,所以我们希望避免分配内存。所以我会写一个small_task< Signature, size_t sz, size_t algn >
。它创建一个大小为 sz
且对齐方式为 algn
的缓冲区,用于存储已擦除的对象。
它还存储了一个移动器、一个破坏器和一个调用函数指针。这些指针可以在本地 small_task
(最大位置)内,也可以在手动 struct vtable { /*...*/ } const* table
.
template<class Sig, size_t sz, size_t algn>
struct small_task;
template<class R, class...Args, size_t sz, size_t algn>
struct small_task<R(Args...), sz, algn>{
struct vtable_t {
void(*mover)(void* src, void* dest);
void(*destroyer)(void*);
R(*invoke)(void const* t, Args&&...args);
template<class T>
static vtable_t const* get() {
static const vtable_t table = {
[](void* src, void*dest) {
new(dest) T(std::move(*static_cast<T*>(src)));
},
[](void* t){ static_cast<T*>(t)->~T(); },
[](void const* t, Args&&...args)->R {
return (*static_cast<T const*>(t))(std::forward<Args>(args)...);
}
};
return &table;
}
};
vtable_t const* table = nullptr;
std::aligned_storage_t<sz, algn> data;
template<class F,
class dF=std::decay_t<F>,
// don't use this ctor on own type:
std::enable_if_t<!std::is_same<dF, small_task>{}>* = nullptr,
// use this ctor only if the call is legal:
std::enable_if_t<std::is_convertible<
std::result_of_t<dF const&(Args...)>, R
>{}>* = nullptr
>
small_task( F&& f ):
table( vtable_t::template get<dF>() )
{
// a higher quality small_task would handle null function pointers
// and other "nullable" callables, and construct as a null small_task
static_assert( sizeof(dF) <= sz, "object too large" );
static_assert( alignof(dF) <= algn, "object too aligned" );
new(&data) dF(std::forward<F>(f));
}
// I find this overload to be useful, as it forces some
// functions to resolve their overloads nicely:
// small_task( R(*)(Args...) )
~small_task() {
if (table)
table->destroyer(&data);
}
small_task(small_task&& o):
table(o.table)
{
if (table)
table->mover(&o.data, &data);
}
small_task(){}
small_task& operator=(small_task&& o){
// this is a bit rude and not very exception safe
// you can do better:
this->~small_task();
new(this) small_task( std::move(o) );
return *this;
}
explicit operator bool()const{return table;}
R operator()(Args...args)const{
return table->invoke(&data, std::forward<Args>(args)...);
}
};
template<class Sig>
using task = small_task<Sig, sizeof(void*)*4, alignof(void*) >;
缺少的另一件事是高质量 void(Args...)
,它不关心传入的可调用对象是否具有 return 值。
以上任务支持移动,不支持复制。添加副本意味着存储的所有内容都必须是可复制的,并且需要 vtable 中的另一个函数(实现类似于 move
,除了 src
是 const
而不是 std::move
)。
使用了少量的 C++14,即 enable_if_t
和 decay_t
别名等。它们可以很容易地用 C++11 编写,或者替换为 typename std::enable_if<?>::type
.
bind
最好用 lambda 代替。我什至在非嵌入式系统上也不使用它。
另一个改进是教它如何处理 smaller/less 对齐的 small_task
,方法是存储它们的 vtable
指针而不是将其复制到 data
缓冲区,并将其包装在另一个 vtable
中。这将鼓励使用刚好足以解决您的问题集的 small_tasks
。
将成员函数转换为函数指针不仅是未定义的行为,而且函数的调用约定通常与成员函数不同。特别是,this
根据某些调用约定在特定寄存器中传递。
这样的差异可能很微妙,当您更改不相关的代码、编译器版本更改或其他任何原因时,就会突然出现。所以我会避免这种情况,除非你别无选择。
如前所述,该平台缺少库。上面的std
的每次使用都是很小的,所以我就写它们:
template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;
using size_t=decltype(sizeof(int));
移动
template<class T>
T&& move(T&t){return static_cast<T&&>(t);}
转发
template<class T>
struct remove_reference:tag<T>{};
template<class T>
struct remove_reference<T&>:tag<T>{};
template<class T>using remove_reference_t=type_t<remove_reference<T>>;
template<class T>
T&& forward( remove_reference_t<T>& t ) {
return static_cast<T&&>(t);
}
template<class T>
T&& forward( remove_reference_t<T>&& t ) {
return static_cast<T&&>(t);
}
衰退
template<class T>
struct remove_const:tag<T>{};
template<class T>
struct remove_const<T const>:tag<T>{};
template<class T>
struct remove_volatile:tag<T>{};
template<class T>
struct remove_volatile<T volatile>:tag<T>{};
template<class T>
struct remove_cv:remove_const<type_t<remove_volatile<T>>>{};
template<class T>
struct decay3:remove_cv<T>{};
template<class R, class...Args>
struct decay3<R(Args...)>:tag<R(*)(Args...)>{};
template<class T>
struct decay2:decay3<T>{};
template<class T, size_t N>
struct decay2<T[N]>:tag<T*>{};
template<class T>
struct decay:decay2<remove_reference_t<T>>{};
template<class T>
using decay_t=type_t<decay<T>>;
is_convertible
template<class T>
T declval(); // no implementation
template<class T, T t>
struct integral_constant{
static constexpr T value=t;
constexpr integral_constant() {};
constexpr operator T()const{ return value; }
constexpr T operator()()const{ return value; }
};
template<bool b>
using bool_t=integral_constant<bool, b>;
using true_type=bool_t<true>;
using false_type=bool_t<false>;
template<class...>struct voider:tag<void>{};
template<class...Ts>using void_t=type_t<voider<Ts...>>;
namespace details {
template<template<class...>class Z, class, class...Ts>
struct can_apply:false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;
namespace details {
template<class From, class To>
using try_convert = decltype( To{declval<From>()} );
}
template<class From, class To>
struct is_convertible : can_apply< details::try_convert, From, To > {};
template<>
struct is_convertible<void,void>:true_type{};
enable_if
template<bool, class=void>
struct enable_if {};
template<class T>
struct enable_if<true, T>:tag<T>{};
template<bool b, class T=void>
using enable_if_t=type_t<enable_if<b,T>>;
result_of
namespace details {
template<class F, class...Args>
using invoke_t = decltype( declval<F>()(declval<Args>()...) );
template<class Sig,class=void>
struct result_of {};
template<class F, class...Args>
struct result_of<F(Args...), void_t< invoke_t<F, Args...> > >:
tag< invoke_t<F, Args...> >
{};
}
template<class Sig>
using result_of = details::result_of<Sig>;
template<class Sig>
using result_of_t=type_t<result_of<Sig>>;
aligned_storage
template<size_t size, size_t align>
struct alignas(align) aligned_storage_t {
char buff[size];
};
is_same
template<class A, class B>
struct is_same:false_type{};
template<class A>
struct is_same<A,A>:true_type{};
live example,我需要每个 std
库模板大约十几行。
我会将此 "std library reimplementation" 放入 namespace notstd
以明确发生了什么。
如果可以,请使用将相同功能折叠在一起的链接器,例如 gold 链接器。模板元编程可能会导致二进制膨胀,而没有可靠的链接器来剥离它。
在尝试手动编写所有 STL 内容之前,我尝试使用编译器本身已经拥有的 STL。因为您使用的大多数 STL 代码只是 header,所以我只是简单地包含它并做一些小改动来编译它们。事实上,id 花了 10 分钟准备 link!
我用的是avr-gcc-5.2.0版本,没有任何问题。我没有旧安装,我相信在几分钟内安装实际版本比修复旧版本的问题更容易。
在为 avr 编译你的示例代码后,我得到 link 个错误:
build-check-std-a520-nomemdbg-os-dynamic-noncov/main.o: In function `std::__throw_bad_function_call()':
/home/krud/own_components/avr_stl/avr_stl009/testing/main.cpp:42: undefined reference to `operator delete(void*, unsigned int)'
/home/krud/own_components/avr_stl/avr_stl009/testing/main.cpp:42: undefined reference to `operator delete(void*, unsigned int)'
collect2: error: ld returned 1 exit status
只需编写自己的 __throw_bad_function_call
即可摆脱 link 问题。
对我来说,编写自己的 STL 实现真的没有意义。这里我简单地使用了来自编译器安装(gcc 5.2.0)的headers。