【发布时间】:2021-10-25 19:36:54
【问题描述】:
鉴于以下设置:
// ***** Library Code *****
#include <concepts>
template <std::invocable CbT>
struct delegated {
explicit constexpr delegated(CbT cb) : cb_(std::move(cb)) {}
private:
[[no_unique_address]] CbT cb_;
};
// ***** User Code *****
#include <iostream>
namespace {
inline constexpr void func() {}
}
struct MyFunc {
constexpr void operator()() const {}
};
int main() {
void (*func_ptr)() = func;
auto from_func = delegated{func};
auto from_func_ptr = delegated{func_ptr};
auto from_lambda = delegated{[](){}};
auto from_functor = delegated{MyFunc{}};
std::cout << "func: " << sizeof(from_func) << "\n";
std::cout << "func_ptr: " << sizeof(from_func_ptr) << "\n";
std::cout << "lambda: " << sizeof(from_lambda) << "\n";
std::cout << "functor: " << sizeof(from_functor) << "\n";
}
它在 GCC-x86-64 (See on godbolt) 上产生:
func: 8 <----- Unfortunate
func_ptr: 8 <----- Fair enough
lambda: 1 <----- Neat
functor: 1 <----- Neat
这些都不是特别令人惊讶的。
但是,令人沮丧的是,未衰减的 lambda 比使用函数更可取。并且添加注释 delegated{[]{func();}} 减少存储开销并不完全是用户友好的,并且导致库界面非常糟糕。
有没有办法消除func 情况下的存储开销,同时保持面向用户的 API 一致?
我目前的怀疑是,如果不求助于宏,这是不可能的,因为func 没有或衰减为任何可以将其与具有相同签名的其他函数区分开来的类型。我希望我忽略了一些东西。
NB我知道类似delegated<func>() 的东西是有可能的,但除非我可以阻止delegated{func} 同时仍然允许delegated{func_ptr},否则这实际上毫无意义。
编辑:稍微澄清一下上下文:我在图书馆写delegated,我不希望该图书馆的用户担心这一点。或者至少让该过程由编译器辅助,而不是依赖于文档。
【问题讨论】:
-
你会反对拥有工厂功能,并像
auto my_delegate = make_delegate(something);一样使用它吗? -
@NathanOliver 那太好了! (只要它不是宏)我在发布问题之前朝那个方向尝试了一些事情,但还没有运气。
-
这个存储开销发生在哪里?它只在调用时发生在堆栈上吗?因为存储 lambda 将需要更大的开销。您是否重视指令代码的大小? lambda 的成本将大于堆栈上单个函数指针的成本。简而言之,您要处理的实际*成本是多少?这里是 0?一个数字大于另一个数字并不是实际成本。
-
@Frank 所以,一个无状态的 lambda 对象必须在调用它的所有代码的程序代码中存储被调用的代码。对于函数指针,相同的调用代码可以用于使用它的每个调用代码。如果您的优化代码实际上没有跟随指针,那么您的编译器正在证明它正在调用哪个指针并实际使用指针跳过。对我来说,这是非常有力的证据,您将指针放在堆上是一个设计缺陷。无状态 lambda 的多个副本……毫无意义?
-
另一方面,如果您正在对
delegated对象进行类型擦除(因此编译器不知道该函数的作用),它不是以节省开销的方式处理函数指针很棘手;特别是如果您愿意破解 ABI。因为擦除开销的过程有一些回旋余地。