这对我来说是一个非常重要的问题,因为它现在是一个瓶颈,我正在尝试研究解决问题的可能方法:我需要constexpr构造一个我正在使用的相当简单的类std::function-like。 然而,它使用的是对齐存储,这样我们就可以配置指针大小的捕获元素数量。 我们就叫它函数吧。
https://github.com/fwsgonzo/libriscv/blob/master/lib/libriscv/util/function.hpp#l91
具体地说,我正在使用函数,最多捕获1个指针。 通常是“这个”。 这些函数正在奇妙地工作,如果你试图捕获太多,它们将不会编译。
问题是它们必须在运行时构造,而且它们太多了,以至于它们使用了大约3500纳秒(3.5微)的时间,这对于我的用例来说是一个永恒的时间。 我必须找到一种方法来降低这种安装成本,所以自然的方法就是研究我是否可以在编译时构造它们。
我无法这样做,编译器直接告诉我,使用placement new的构造函数不能在constexpr上下文中使用。 这个问题讲述了同样的故事:
C++constexpr就地对齐存储结构
您可以在此处看到有问题的语句:https://github.com/fwsgonzo/libriscv/blob/master/lib/libriscv/util/function.hpp#L148
template<typename Callable>
Function (Callable callable) noexcept
{
static_assert(sizeof(Callable) <= FunctionStorageSize,
"Callable too large (greater than FunctionStorageSize)");
static_assert(std::is_trivially_copy_constructible_v<Callable>,
"Callable not trivially copy constructible");
static_assert(std::is_trivially_destructible_v<Callable>,
"Callable not trivially destructible");
m_func_ptr = &trampoline<Callable>;
new(reinterpret_cast<Callable *>(m_storage.data)) Callable(callable);
}
我使用的是C++20,我对如何解决这个问题的建议持开放态度。 既然这些函数有一个固定大小的捕获存储,只有一个函数指针,那么有没有可能在编译时构造这些函数呢? 不应由此产生堆分配。
使用C++20,如果将callable
类型的约束增加为trivially_copyable
,则可以使用bit_caster
。 您还必须为所有可能的对象大小定义一个包含aligned_storage
类型成员的联合。
不幸的是,我认为还没有bit_cast
的constexpr实现。
如果callable
指定了指向对象类型的指针,则可以声明constexpr构造函数:
template<typename Callable>
constexpr
Function (Callable * callable) noexcept
m_pointer {callable}
m_func_ptr = &trampoline <Callable>
{}
//declare the union
union {
void * m_pointer;
Storage m_storage;
};
//end an overload trampoline specialized for pointer to object.
我也创建了自己的类型擦除函数。 它不是constexpr,因为我需要使用placement new或std::memcopy来填充我的存储。
主要思想是使用一个非捕获lambda为“蹦床代”,也许你可以使用它。 优化后的,生成的程序集在我眼里看起来真的很不错。。。 神箭
#include <iostream>
#include <cstring>
namespace Test
{
template<typename Return, typename... Args>
using InvokeFktPtr = Return(*)(const void*, Args...);
template <
typename Fkt
>
class SingleCastDelegate;
template <
typename ReturnType,
typename... Args
>
class SingleCastDelegate<ReturnType(Args...)>
{
private:
InvokeFktPtr<ReturnType, Args...> invokeFktPtr;
private:
static constexpr size_t max_lambda_size = 4 * sizeof(void*);
std::byte storage[max_lambda_size];
private:
constexpr const void* GetData() const
{
return std::addressof(storage[0]);
}
constexpr void* GetData()
{
return std::addressof(storage[0]);
}
public:
template<
typename Lambda
,typename PureLambda = std::remove_reference_t<Lambda>
>
inline SingleCastDelegate(Lambda&& lambda)
{
constexpr auto lambdaSize = sizeof(PureLambda);
static_assert(lambdaSize <= sizeof(void*) * 4);
//add some static_asserts... (it must be trivial...)
//placement new is not constexpr, or?
new(std::addressof(storage)) PureLambda(lambda);
invokeFktPtr = [](const void* data, Args... args)
{
const PureLambda& l = *static_cast<const PureLambda*>(data);
return l(args...);
};
}
template<
typename... CustomArgs
>
using FktPtr = ReturnType(*)(CustomArgs...);
template<
typename... CustomArgs
, typename = typename std::enable_if_t<std::is_invocable_v<FktPtr<Args...>, CustomArgs...>>
>
constexpr ReturnType operator()(CustomArgs&&... args) const
{
return invokeFktPtr(GetData(), std::forward<CustomArgs>(args)...);
}
};
}
int main()
{
int i = 42;
auto myFkt = [=](){
std::cout << i;
};
auto myOtherFkt = [=](){
std::cout << i * 2;
};
Test::SingleCastDelegate<void()> fkt = Test::SingleCastDelegate<void()>{ myFkt };
fkt();
fkt = myOtherFkt;
fkt();
return 0;
}
虽然C++20确实允许您在constexpr上下文中动态分配内存,但不允许在编译时分配的内存泄漏到运行时执行中。 因此constexpr分配必须静态绑定到常量表达式求值。
而且即使有C++20的特性,也不能在编译时使用placement new。