c++ - 使用函数作为回调时,有没有办法避免存储开销?

标签 c++ c++20

鉴于以下设置:

// ***** 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没有或衰减为任何可以将其与具有相同签名的其他函数区分开来的类型。我希望我忽略了一些东西。
注: 我得到了 delegated<func>() 的内容。有可能,但除非我能阻止delegated{func}同时仍然允许delegated{func_ptr} ,那么这实际上是没有意义的。
编辑:稍微澄清一下上下文:我正在写delegated在图书馆里,我不希望该图书馆的用户担心这一点。或者至少让这个过程得到编译器的帮助,而不是依赖于文档。

最佳答案

没有函数类型的对象。类型将被调整为函数指针,这就是为什么你 delegated{func}delegated{func_ptr}是完全相同的东西,前者不能更小。
将函数调用包装在函数对象(lambda,如果您愿意的话)中,以避免函数指针的开销。

如果您想防止在用户尝试传递函数时意外使用调整/衰减函数指针的情况,那么您可以对函数引用使用已删除的重载。我不知道 CTAD 是如何实现的,但是如果你提供一个函数接口(interface),它可以这样完成:

constexpr auto
make_delegated(std::invocable auto CbT)
{
    return delegated{std::move(CbT)};
}

template<class... Args>
constexpr auto
make_delegated(auto (&cb)(Args...)) = delete;

编辑:将想法与 Human-Compiler's answer 结合起来
template <auto CbT>
constexpr auto
make_delegated_fun() { 
  return delegated{ []{ CbT(); } };
}

constexpr auto
make_delegated(std::invocable auto CbT)
{
    return delegated{std::move(CbT)};
}

template<class... Args>
constexpr auto
make_delegated(auto (&cb)(Args...)) {
    // condition has to depend on template argument;
    // just false would cause the assert to trigger without overload being called.
    static_assert(!std::is_reference_v<decltype(cb)>, "please use make_delegated_fun");
};


auto from_func1 = make_delegated(func);        // fails to compile
auto from_func2 = make_delegated_fun<func>();  // OK
auto from_func_ptr = make_delegated(func_ptr); // OK, pointer overhead
auto from_lambda = make_delegated([](){});     // OK
auto from_functor = make_delegated(MyFunc{});  // OK
警告,这会阻止跟随,并且该示例无法使用 make_delegated_fun否则,该消息将具有误导性。该示例可以很容易地重写为使用函数指针或捕获 lambda:
auto& fun_ref = condition ? fun1 : fun2;
make_delegated(fun_ref);       // fails to compile, suggests make_delegated_fun
make_delegated_fun<fun_ref>(); // fails to compile, not constexpr
make_delegated(&fun_ref);      // OK, pointer overhead

关于c++ - 使用函数作为回调时,有没有办法避免存储开销?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68926707/

相关文章:

c++ - 为什么我不能从迭代器构造 std::span ?

c++ - 函数指针与函数引用

c++ - 推导的模板 arg 和自动 arg 之间有什么区别吗?

c++ - std::set 是否可以与 std::partial_ordering 进行比较的类型正常工作?

c++ - 在运行时从指向基类的指针获取对象的类型

c++ - 如何对派生类进行限制?

c++ - 在 C++ 中强制执行精确的可调用签名

c++ - 用于使用 tesseract 和 opencv 的 cMakefile

c++ - 赋予容器其子代的所有权,但让子代使用智能指针存储对其父代的引用

c++ - 是否可以使用 `std::set_intersection` 来检查两个集合是否有任何共同元素?