Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement P1132R7 out_ptr() and inout_ptr() #1998

Merged
merged 11 commits into from
Jul 30, 2021
161 changes: 161 additions & 0 deletions stl/inc/memory
Original file line number Diff line number Diff line change
Expand Up @@ -4094,6 +4094,167 @@ public:
};
#endif // _HAS_CXX20

#if _HAS_CXX23 && defined(__cpp_lib_concepts)
template <class _Ty>
struct _Pointer_of_helper {};

// clang-format off
template <class _Ty>
requires requires { typename _Ty::pointer; }
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved
struct _Pointer_of_helper<_Ty> {
using type = typename _Ty::pointer;
};

template <class _Ty>
requires (!requires { typename _Ty::pointer; } && requires { typename _Ty::element_type; })
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved
struct _Pointer_of_helper<_Ty> {
using type = typename _Ty::element_type*;
};

template <class _Ty>
requires (!requires { typename _Ty::element_type; } && !requires { typename _Ty::pointer; }
&& requires { typename pointer_traits<_Ty>::element_type; })
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved
struct _Pointer_of_helper<_Ty> {
using type = typename pointer_traits<_Ty>::element_type*;
};
// clang-format on

template <class _Ty>
using _Pointer_of = typename _Pointer_of_helper<_Ty>::type;

template <class _Ty, class _Uty>
struct _Pointer_of_or_helper {
using type = _Uty;
};

// clang-format off
template <class _Ty, class _Uty>
requires requires { typename _Pointer_of<_Ty>; }
struct _Pointer_of_or_helper<_Ty, _Uty> {
using type = _Pointer_of<_Ty>;
};
// clang-format on

template <class _Ty, class _Uty>
using _Pointer_of_or = typename _Pointer_of_or_helper<_Ty, _Uty>::type;
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved

template <class _SmartPtr, class _Sp, class _Pointer, class... _ArgsT>
concept _Resettable_pointer = requires(_SmartPtr& _Smart_ptr, _Pointer _Ptr, _ArgsT&&... _Args) {
_Smart_ptr.reset(static_cast<_Sp>(_Ptr), _STD forward<_ArgsT>(_Args)...);
};
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved

template <class _SmartPtr, class _Pointer, class... _ArgsT>
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
class out_ptr_t {
static_assert(!_Is_specialization_v<_SmartPtr, shared_ptr> || sizeof...(_ArgsT) != 0,
"out_ptr_t with shared_ptr requires a deleter");
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved

public:
explicit out_ptr_t(_SmartPtr& _Smart_ptr_, _ArgsT... _Args_)
: _Smart_ptr(_Smart_ptr_), _Args(_STD forward<_ArgsT>(_Args_)...) {}

out_ptr_t(const out_ptr_t&) = delete;

~out_ptr_t() {
if (_Ptr) {
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved
_STD apply(
[&](auto&&... _Args_) {
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved
using _Sp = _Pointer_of_or<_SmartPtr, _Pointer>;
if constexpr (_Resettable_pointer<_SmartPtr, _Sp, _Pointer, _ArgsT...>) {
_Smart_ptr.reset(static_cast<_Sp>(_Ptr), _STD forward<_ArgsT>(_Args_)...);
} else {
_Smart_ptr = _SmartPtr(static_cast<_Sp>(_Ptr), _STD forward<_ArgsT>(_Args_)...);
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
}
},
_STD move(_Args));
}
}

operator _Pointer*() const noexcept {
return _STD addressof(const_cast<_Pointer&>(_Ptr));
}

operator void**() const noexcept requires(!is_same_v<_Pointer, void*>) {
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
static_assert(is_pointer_v<_Pointer>, "out_ptr_t's operator void** requires Pointer to be a pointer");
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved
return reinterpret_cast<void**>(_STD addressof(const_cast<_Pointer&>(_Ptr)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the precondition that we should not have called operator _Pointer* Do we care enough to actually validate this somehow? It would most likely cost some memory so I am totally fine with nasal demons emerging

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's worth validating. It will be used as foo(out_ptr(my_ptr)) 99.9% of the time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Adam. It's not so much the memory as it is having the ABI of out_ptr differ between debug and release modes. We already do that with many types, but doing it here will probably cause more harm than good, given Adam's point about common usage

}

private:
_SmartPtr& _Smart_ptr;
tuple<_ArgsT...> _Args;
_Pointer _Ptr{};
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved
};

template <class _Pointer = void, class _SmartPtr, class... _ArgsT>
_NODISCARD auto out_ptr(_SmartPtr& _Smart_ptr, _ArgsT&&... _Args) {
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
if constexpr (is_void_v<_Pointer>) {
return out_ptr_t<_SmartPtr, _Pointer_of<_SmartPtr>, _ArgsT&&...>(_Smart_ptr, _STD forward<_ArgsT>(_Args)...);
} else {
return out_ptr_t<_SmartPtr, _Pointer, _ArgsT&&...>(_Smart_ptr, _STD forward<_ArgsT>(_Args)...);
}
}

template <class _SmartPtr, class _Pointer, class... _ArgsT>
class inout_ptr_t {
static_assert(!_Is_specialization_v<_SmartPtr, shared_ptr>, "inout_ptr_t doesn't work with shared_ptr");
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved

static auto _Get_ptr(_SmartPtr& _Smart_ptr) {
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
if constexpr (is_pointer_v<_SmartPtr>) {
return _Smart_ptr;
} else {
return _Smart_ptr.get();
}
}

public:
explicit inout_ptr_t(_SmartPtr& _Smart_ptr_, _ArgsT... _Args_)
: _Smart_ptr(_Smart_ptr_), _Args(_STD forward<_ArgsT>(_Args_)...), _Ptr(_Get_ptr(_Smart_ptr_)) {}
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved

inout_ptr_t(const inout_ptr_t&) = delete;

~inout_ptr_t() {
if (_Ptr) {
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved
_STD apply(
[&](auto&&... _Args_) {
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved
using _Sp = _Pointer_of_or<_SmartPtr, _Pointer>;
if constexpr (is_pointer_v<_SmartPtr>) {
_Smart_ptr = _SmartPtr(static_cast<_Sp>(_Ptr), _STD forward<_ArgsT>(_Args_)...);
} else if constexpr (_Resettable_pointer<_SmartPtr, _Sp, _Pointer, _ArgsT...>) {
_Smart_ptr.release();
_Smart_ptr.reset(static_cast<_Sp>(_Ptr), _STD forward<_ArgsT>(_Args_)...);
} else {
_Smart_ptr.release();
_Smart_ptr = _SmartPtr(static_cast<_Sp>(_Ptr), _STD forward<_ArgsT>(_Args_)...);
}
},
_STD move(_Args));
}
}

operator _Pointer*() const noexcept {
return _STD addressof(const_cast<_Pointer&>(_Ptr));
}

operator void**() const noexcept requires(!is_same_v<_Pointer, void*>) {
static_assert(is_pointer_v<_Pointer>, "inout_ptr_t's operator void** requires Pointer to be a pointer");
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
return reinterpret_cast<void**>(_STD addressof(const_cast<_Pointer&>(_Ptr)));
}

private:
_SmartPtr& _Smart_ptr;
tuple<_ArgsT...> _Args;
_Pointer _Ptr;
};

template <class _Pointer = void, class _SmartPtr, class... _ArgsT>
_NODISCARD auto inout_ptr(_SmartPtr& _Smart_ptr, _ArgsT&&... _Args) {
if constexpr (is_void_v<_Pointer>) {
return inout_ptr_t<_SmartPtr, _Pointer_of<_SmartPtr>, _ArgsT&&...>(_Smart_ptr, _STD forward<_ArgsT>(_Args)...);
} else {
return inout_ptr_t<_SmartPtr, _Pointer, _ArgsT&&...>(_Smart_ptr, _STD forward<_ArgsT>(_Args)...);
}
}
#endif // _HAS_CXX23 && defined(__cpp_lib_concepts)

#if _HAS_TR1_NAMESPACE
namespace _DEPRECATE_TR1_NAMESPACE tr1 {
using _STD allocate_shared;
Expand Down
3 changes: 3 additions & 0 deletions stl/inc/yvals_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@
// P1831R1 Deprecating volatile In The Standard Library
// Other C++20 deprecation warnings

// _HAS_CXX23 controls:
// P1132R7 out_ptr(), inout_ptr()
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved

// Parallel Algorithms Notes
// C++ allows an implementation to implement parallel algorithms as calls to the serial algorithms.
// This implementation parallelizes several common algorithm calls, but not all.
Expand Down
4 changes: 4 additions & 0 deletions tests/std/tests/P1132R7_out_ptr/env.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_latest_matrix.lst
201 changes: 201 additions & 0 deletions tests/std/tests/P1132R7_out_ptr/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <cassert>
#include <memory>

using namespace std;

void test_raw_ptr() {
int i = 5;
int* int_ptr = &i;

{
const auto f = [](int** ptr) {
assert(**ptr == 5);
static int i = 7;
*ptr = &i;
};

f(inout_ptr(int_ptr));

assert(*int_ptr == 7);
}
{
const auto f = [](void** ptr) {
assert(*reinterpret_cast<int*>(*ptr) == 7);
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved
static int i = 15;
*ptr = &i;
};

f(inout_ptr(int_ptr));

assert(*int_ptr == 15);
}
}

void test_shared_ptr() {
shared_ptr<int> int_ptr = make_shared<int>(5);

{
const auto f = [](int** ptr) { *ptr = new int(7); };

f(out_ptr(int_ptr, default_delete<int>{}));

assert(*int_ptr == 7);
}
{
const auto f = [](void** ptr) { *ptr = new int(15); };

f(out_ptr(int_ptr, default_delete<int>{}));

assert(*int_ptr == 15);
}

int count = 0;
const auto deleter = [&count](int* ptr) {
++count;
delete ptr;
};

{
const auto f = [](int** ptr) { *ptr = new int(23); };

f(out_ptr(int_ptr, deleter));

assert(count == 0);
assert(*int_ptr == 23);
}
{
const auto f = [](void** ptr) { *ptr = new int(32); };

f(out_ptr(int_ptr, deleter));

assert(count == 1);
assert(*int_ptr == 32);
}

int_ptr.reset();
assert(count == 2);
}

template <class Ptr, class Pointer = void, class... Args>
void test_smart_ptr(Args&&... args) {
Ptr int_ptr{new int(5)};

{
const auto f = [](int** ptr) {
assert(**ptr == 5);
*ptr = new int(7);
};

f(inout_ptr<Pointer>(int_ptr, forward<Args>(args)...));
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved

assert(*int_ptr == 7);
}
{
const auto f = [](int** ptr) { *ptr = new int(12); };

f(out_ptr<Pointer>(int_ptr, forward<Args>(args)...));

assert(*int_ptr == 12);
}
{
const auto f = [](void** ptr) {
assert(*reinterpret_cast<int*>(*ptr) == 12);
*ptr = new int(15);
};

f(inout_ptr<int*>(int_ptr, forward<Args>(args)...));

assert(*int_ptr == 15);
}
{
const auto f = [](void** ptr) { *ptr = new int(19); };

f(out_ptr<int*>(int_ptr, forward<Args>(args)...));

assert(*int_ptr == 19);
}
}

struct reset_tag {};

struct resettable_ptr {
using element_type = int; // test having element_type only

unique_ptr<int> ptr;

explicit resettable_ptr(int* p) : ptr(p) {}

void reset(int* p, reset_tag) {
ptr.reset(p);
}

auto operator*() const {
return *ptr;
}

auto get() const {
return ptr.get();
}

void release() {
ptr.release();
}
};

struct constructible_ptr {
using pointer = int*; // test having pointer only

unique_ptr<int> ptr;

explicit constructible_ptr(int* p) : ptr(p) {}
explicit constructible_ptr(int* p, reset_tag) : ptr(p) {}

auto operator*() const {
return *ptr;
}

auto get() const {
return ptr.get();
}

void release() {
ptr.release();
}
};

struct resettable_ptr2 : public resettable_ptr {
AdamBucior marked this conversation as resolved.
Show resolved Hide resolved
using resettable_ptr::resettable_ptr;

private:
using resettable_ptr::element_type;
};

template <>
struct pointer_traits<resettable_ptr2> {
using element_type = int; // test having only pointer_traits::element_type
};

struct constructible_ptr2 : public constructible_ptr {
using constructible_ptr::constructible_ptr;

private:
using constructible_ptr::pointer;
};

template <>
struct pointer_traits<constructible_ptr2> {
// test having nothing
};

int main() {
test_raw_ptr();
test_shared_ptr();
test_smart_ptr<unique_ptr<int>>();
test_smart_ptr<resettable_ptr>(reset_tag{});
test_smart_ptr<constructible_ptr>(reset_tag{});
test_smart_ptr<resettable_ptr2>(reset_tag{});
test_smart_ptr<constructible_ptr2, int*>(reset_tag{});
}