ben-craig / freestanding_proposal

19 stars 0 forks source link

`consteval` allocating facilities in freestanding implementations? P3295 #5

Open frederick-vs-ja opened 11 months ago

frederick-vs-ja commented 11 months ago

Edit: P3295R0 addresses most of library part of this issue.


It seems that we can make std::allocator and its friends freestanding, with allowing the allocate etc. member functions to be consteval for freestanding implementations.

Thanks to P2564R3, once the allocate and its friends arestd::allocator are made consteval, allocation and deallocation in the downstream facilities (std::vector, std::string, etc.) would become only usable in constant evaluation, which avoids touching the runtime heap.

Proof-by-concept example: ~(although clang is a bit buggy at this moment)~

#include <cstddef>
#include <memory>
#include <vector>
#include <type_traits>
#include <utility>

namespace imm {
    template<class T>
    struct allocator {
        using value_type = T;
        using size_type = std::size_t;
        using difference_type = std::ptrdiff_t;

        consteval T* allocate(size_type n)
        {
            return std::allocator<T>{}.allocate(n);
        }

#if __cpp_lib_allocate_at_least >= 202302L
        consteval std::allocation_result<T*> allocate_at_least(size_type n)
        {
            return std::allocator<T>{}.allocate_at_least(n);
        }
#endif // __cpp_lib_allocate_at_least >= 202302L

        consteval void deallocate(T* p, size_type n)
        {
            return std::allocator<T>{}.deallocate(p, n);
        }
    };

    template<class T>
    using vector = std::vector<T, allocator<T>>;

    template<class T, class... Args>
        requires (std::is_object_v<T> && !std::is_array_v<T>)
    consteval std::unique_ptr<T> make_unique(Args&&... args)
    {
        return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
    }
}

template<class = void> // either consteval or constexpr but templated
constexpr bool test_vector()
{
    return imm::vector<int>(42).size() == 42;
}

template<class = void> // either consteval or constexpr but templated
constexpr bool test_make_unique()
{
    return !*imm::make_unique<bool>();
}

int main()
{
    static_assert(test_vector());
    static_assert(test_make_unique());
}
ben-craig commented 11 months ago

I discuss something along these lines here: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2268r0.html#constexpr_to_consteval

I think the idea can work, but there's a need for someone to test it, and to describe the behavior differences between consteval and constexpr in these cases.

frederick-vs-ja commented 11 months ago

I guess I've made some tests and an incomplete conclusion for the differences.

consteval and immediate-escalation make the function only callable in restrict contexts, while constexpr doesn't. Sometimes the restriction might be considered overly strict.

I think the most counterintuitive affection is that if we make std::allocator and basic_string to freestanding while marking allocator::allocate etc. consteval (we can establish the // freestanding-consteval notion for such a requirement), then a string cannot be manipulated at the runtime even if one can guarantee that the contents always fit into the SSO buffer.


Moreover...

In the core wording, perhaps we can make currently allowed forms of new- and delete-expressions (implementation-definedly) immediate-escalating under freestanding implementations, but


Edit: P2747 addresses non-allocating placement new-expressions. As a result, they shouldn't be immediate-escalating.