Document number |
P3207R0 |
Date |
2024-03-24 |
Reply-to |
Jarrad J. Waterloo <descender76 at gmail dot com>
|
Audience |
Library Evolution Working Group (LEWG) |
More & like
Table of contents
Abstract
Our new and existing reference types could be made to be more like references. This will reduce the misuse of these reference types and make it easier to reason about dangling.
Motivational Example
function_ref
A stripped down function_ref
has been created to better illustrate the requested changes. This new version of function_ref
has been renamed function_ptr
. Its address of operators has been deleted. Further, an alias to a const function_ptr
has been created and named function_ref
.
given
#include <string>
#include <vector>
template< class >
class function_ptr;
template< class R, class... Args >
class function_ptr<R(Args...) noexcept>
{
public:
function_ptr() = delete;
constexpr function_ptr(const function_ptr& other) noexcept = default;
constexpr function_ptr(function_ptr&& other) noexcept = default;
constexpr function_ptr& operator=( const function_ptr& ) noexcept = default;
constexpr function_ptr& operator=( function_ptr&& other ) noexcept = default;
~function_ptr() noexcept = default;
function_ptr* operator&(void) = delete;
function_ptr const *operator&(void) const = delete;
function_ptr volatile *operator&(void) volatile = delete;
function_ptr const volatile *operator&(void) const volatile = delete;
constexpr function_ptr(void* s, R (*f)(void* state, Args...) noexcept) noexcept
: state{s}, thunk{f} {}
R operator()( Args... args ) const noexcept
{
return thunk(state, std::forward<Args>(args)...);
}
private:
void* state;
R (*thunk)(void* state, Args...) noexcept;
};
template< class T >
using function_ref = const function_ptr<T>;
So what do these minor changes buy us.
- By deleting the address of operators, we ensure proper usage of the reference type. Reference types are meant to be passed by copy not by reference. This removes additional avenues of dangling. This is consistent with references in general. If we retained these operators than they should return the address of the referent instead of the address of the reference type.
- Lvalue references are immutable. "A reference is similar to a const pointer such as int* const p (as opposed to a pointer to const such as const int* p)" References and constant pointers are easier to reason about dangling. Since references are not reseatable and constant pointers are not by default reseatable, they are just aliases to the referent that they point to. This means at compile time, once they point to a global, local or temporary, they always point to said global, local or temporary. Further since the constness is baked in, a programmer is less likely to forget to add it.
int main()
{
function_ptr<int (int) noexcept> fp{nullptr,
[](void* state, int i) noexcept { return test(i); }};
fp = {nullptr, [](void* state, int i) noexcept { return test(i + 1); }};
function_ref<int (int) noexcept> fr{nullptr,
[](void* state, int i) noexcept { return test(i); }};
fp = fr;
std::vector<function_ptr<int (int) noexcept>> fps;
return 0;
}
optional
It has been proposed to allow std::optional
to support being a pure reference type: std::optional<T&>
. While one must pause to decide whether this specialization should have its address of operators deleted since std::optional
is an existing type, there shouldn't be any impediment to adding one alias to bake constness in as a convenience. This is especially important as there are plenty of methods in the standard library that current return a reference with undefined behavior which would be better served by returning a const optional<T&>
with defined behavior. Making sure to add const
to our optional<T&>
would also be imporatant for our future proposals such as Better lookups for map and unordered_map
.
given
#include <vector>
#include <optional>
template<class T>
using optional_ref = const optional<T&>;
Here the proposed std::optional<T&>
behaves correctly like a pointer with a constant pointer able to be implicitly converted to a non constant pointer.
int main()
{
int value = 0;
int * p00 = &value;
int const * p10 = &value;
int * const p01 = &value;
int const * const p11 = &value;
p00 = p00;
p00 = p01;
p10 = p00;
p10 = p10;
p10 = p01;
p10 = p11;
return 0;
}
|
int main()
{
int value = 0;
optional<int&> op00{value};
optional<const int&> op10{value};
const optional<int&> op01{value};
const optional<const int&> op11{value};
op00 = op00;
op00 = op01;
op10 = op00;
op10 = op10;
op10 = op01;
op10 = op11;
return 0;
}
|
The optional_ref
alias similary behaves correctly but this time const
is baked in by default.
int main()
{
int value = 0;
optional<int&> op00{value};
optional<const int&> op10{value};
const optional<int&> op01{value};
const optional<const int&> op11{value};
op00 = op00;
op00 = op01;
op10 = op00;
op10 = op10;
op10 = op01;
op10 = op11;
return 0;
}
|
int main()
{
int value = 0;
optional<int&> op00{value};
optional<const int&> op10{value};
optional_ref<int> op01{value};
optional_ref<const int> op11{value};
op00 = op00;
op00 = op01;
op10 = op00;
op10 = op10;
op10 = op01;
op10 = op11;
return 0;
}
|
Where else and where not
Similar changes could be performed on existing pure reference types in the standard library such as std::span
, std::string_view
and std::mdspan
. These changes would not be recommended for hybrid reference types such as std::tuple
and std::variant
should reference support be added.
Language feature?
This could better be implemented as a language feature. I would be glad to write such a proposal, if there was sufficient interest.
[[const_by_default]]
class function_ref;
function_ref fr1;
mutable function_ref fr2;
Summary
Deleting address of operators from pure reference types minimizes the misuse of said types and reduces the avenues to dangling. Ensuring pure reference types are immutable by default makes it easier for programmers to reason about the lifetimes of the referents that these reference type instances points to.
References
Jarrad J. Waterloo <descender76 at gmail dot com>
More & like
Table of contents
Abstract
Our new and existing reference types could be made to be more like references. This will reduce the misuse of these reference types and make it easier to reason about dangling.
Motivational Example
function_ref
A stripped down
function_ref
has been created to better illustrate the requested changes. This new version offunction_ref
has been renamedfunction_ptr
. Its address of operators has been deleted. Further, an alias to aconst function_ptr
has been created and namedfunction_ref
.given
So what do these minor changes buy us.
optional
It has been proposed to allow
std::optional
to support being a pure reference type:std::optional<T&>
[2]. While one must pause to decide whether this specialization should have its address of operators deleted sincestd::optional
is an existing type, there shouldn't be any impediment to adding one alias to bake constness in as a convenience. This is especially important as there are plenty of methods in the standard library that current return a reference with undefined behavior which would be better served by returning aconst optional<T&>
with defined behavior. Making sure to addconst
to ouroptional<T&>
would also be imporatant for our future proposals such asBetter lookups for map and unordered_map
[3].given
Here the proposed
std::optional<T&>
[2:1] behaves correctly like a pointer with a constant pointer able to be implicitly converted to a non constant pointer.The
optional_ref
alias similary behaves correctly but this timeconst
is baked in by default.Where else and where not
Similar changes could be performed on existing pure reference types in the standard library such as
std::span
,std::string_view
andstd::mdspan
. These changes would not be recommended for hybrid reference types such asstd::tuple
andstd::variant
should reference support be added.Language feature?
This could better be implemented as a language feature. I would be glad to write such a proposal, if there was sufficient interest.
Summary
Deleting address of operators from pure reference types minimizes the misuse of said types and reduces the avenues to dangling. Ensuring pure reference types are immutable by default makes it easier for programmers to reason about the lifetimes of the referents that these reference type instances points to.
References
https://isocpp.org/wiki/faq/references#reseating-refs ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2988r3.pdf ↩︎ ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3091r0.html ↩︎