提问者:小点点

C++使用constexpr使放置新的对齐存储可初始化


这对我来说是一个非常重要的问题,因为它现在是一个瓶颈,我正在尝试研究解决问题的可能方法:我需要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,我对如何解决这个问题的建议持开放态度。 既然这些函数有一个固定大小的捕获存储,只有一个函数指针,那么有没有可能在编译时构造这些函数呢? 不应由此产生堆分配。


共3个答案

匿名用户

使用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。

相关问题


MySQL Query : SELECT * FROM v9_ask_question WHERE 1=1 AND question regexp '(c++|constexpr|放置|新|对齐|存储|初始化)' ORDER BY qid DESC LIMIT 20
MySQL Error : Got error 'repetition-operator operand invalid' from regexp
MySQL Errno : 1139
Message : Got error 'repetition-operator operand invalid' from regexp
Need Help?