j***@gmail.com
2016-10-11 03:10:37 UTC
Hi,
This message highlights my thoughts about so-called "smart references" and
how best to support such a concept in the standard library.
I am usually excited by any upcoming or proposed additions to C++, but the operator
dot proposal
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4173.pdf>
concerns me. It strikes me as adding significant complexity to the language
to support what amounts to a bit of syntactic sugar. Of course, given that
the proposal essentially adds support for compile-time function overriding,
I'm sure it could be used to do all sorts of "clever" things, but I digress.
I do see the need for official support for "smart references" in C++.
Modern C++ orthodoxy says that raw pointers should be avoided in high-level
code. C++ has always recommended std::string over C-style strings and
std::vector over C-style dynamic arrays. The smart pointers std::unique_ptr
and std::shared_ptr are now established for resource management, and
std::array for statically sized arrays. The upcoming std::optional replaces
some potential uses of raw pointers, std::string_view means we no longer
need to deal with raw pointers into std::string objects, and the proposed
std::array_view will do the same for arrays.
However, one use of raw pointers still remains: as non-owning references.
When I say references, I don't mean just C++ references; I mean any use of
C++ pointers or C++ references to refer to an object which is not owned. A
common pattern is to use a C++ reference where a reference is mandatory,
and a C++ pointer where a reference is optional (using nullptr as the
"missing" state).
void foo(bar const& mandatory);
void foo(bar const* optional);
However, there are a few shortcomings to this approach:
1. It lacks semantics. The meaning of a raw pointer is overloaded; are
we passing a reference, an array, an "iterator", an "owning" pointer, or a
string (in the case of char const*)? Even if we know it is a reference,
is it really optional? Some people don't like representing references using
C++ references, so they used pointers that cannot be null instead. The GSL
is addressing this somewhat with the gsl::not_null annotation, but this
is hardly optimal. Speaking of references, they also have overloaded
meaning: passing by reference can be for performance, or because a
reference is genuinely needed (and maybe stored somewhere).
2. C++ references cannot be reassigned. I understand this is one of the
motivating forces behind the operator dot proposal. I agree it is a big
problem.
What we need are some wrapper types which can represent references
(mandatory or optional) to arbitrary objects. One proposal
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4282> already
exists which kind of addresses this need: std::observer_ptr (a.k.a. The
World's Dumbest Smart Pointer). This is nice because it conveys meaning (it
"observes"; it doesn't "own"), but it falls short in a few other areas.
Firstly, it doesn't support mandatory references (pointers can be null).
Secondly, it still has "pointer" semantics. I would like my smart
references to have "reference" semantics; in other words, I do not want to
have to enter the realm of C++ pointers; I do not want to have to do this:
std::observer_ptr<int> o = &i; // address-of operator: yuck!
The problem is that std::observer_ptr is an "observing" pointer modelled on
the "owning" smart pointers, hence why is is kind of dumb. I propose that
we need wrappers designed from the ground up to model some kind of
"reference" concept. However, since std::ref is already taken, and for
other reasons which should become apparent later, I suggest not using the
term "reference". We could follow std::observer_ptr and use the term
"observer", but I feel that the name "view" is more appropriate, having
been coined by std::string_view as a term for something which is a
non-owning reference to another object. Thus, I suggest std::view:
std::view<int> v = i; // no pointer semantics: yay!
But, I hear you say, don't we need the operator dot proposal to get our
"reference-y" semantics? Well, to get C++ reference semantics, yes we do.
However, it is not required. Consider this:
std::view<int> v = i;
v = j; // reassignment of the view
*v = 42; // assignment of the referenced object
This has the advantage that assignment of the view and assignment of the
referenced object are clearly disambiguated; there is no need to change the
language, and there is less potential for confusing. But, but, I hear you
protest, this is pointer semantics! Indeed, the operator dot proposal
mentions this as a motivating factor. However, there is already precedent
for non-pointer usage of operator* and operator-> in std::optional:
std::optional<int> o = i;
o = j; // reassignment of the optional
*o = 42; // assignment of the underlying object
Indeed, std::view would have the exact same semantics as std::optional.
Thus, we have a "smart reference" type which requires no change to the
language, has no null/missing state, supports reassignment, has non-pointer
semantics, and has usage patterns consistent with other types already in
the standard library. Optional references can also be implemented naturally:
std::optional_view<int> o; // can be default constructed
o = i; // reassignment of the optional
o = std::view<int>(j); // optional views can be assigned from views, but
not vice-versa
if (o) {
*o = 42; // assignment of the underlying object
}
o = std::nullopt; // reuse of existing library features: yay!
I think this is pretty neat. And none of this required support for
overloading operator dot. Indeed, I feel that the the ambiguous nature of
operator= for a type which overloads operator dot may be a red flag that
something is amiss. However, I'm sure this has been discussed at length,
and perhaps the operator dot proposal is orthogonal to what I am suggesting
here. I just wanted to show that we can already address a number of
concerns without adding complexity to the language, and in my opinion, far
more cleanly than any solution involving operator dot.
One final thought: would the existence of std::optional_view make
std::observer_ptr redundant? My feeling is "perhaps", but I wouldn't be
surprised the answer were "no".
Thanks for taking the time to read my musings. Please let me know your
opinions, and if anything like this has been suggested before.
Kind regards,
Joseph Thomson
This message highlights my thoughts about so-called "smart references" and
how best to support such a concept in the standard library.
I am usually excited by any upcoming or proposed additions to C++, but the operator
dot proposal
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4173.pdf>
concerns me. It strikes me as adding significant complexity to the language
to support what amounts to a bit of syntactic sugar. Of course, given that
the proposal essentially adds support for compile-time function overriding,
I'm sure it could be used to do all sorts of "clever" things, but I digress.
I do see the need for official support for "smart references" in C++.
Modern C++ orthodoxy says that raw pointers should be avoided in high-level
code. C++ has always recommended std::string over C-style strings and
std::vector over C-style dynamic arrays. The smart pointers std::unique_ptr
and std::shared_ptr are now established for resource management, and
std::array for statically sized arrays. The upcoming std::optional replaces
some potential uses of raw pointers, std::string_view means we no longer
need to deal with raw pointers into std::string objects, and the proposed
std::array_view will do the same for arrays.
However, one use of raw pointers still remains: as non-owning references.
When I say references, I don't mean just C++ references; I mean any use of
C++ pointers or C++ references to refer to an object which is not owned. A
common pattern is to use a C++ reference where a reference is mandatory,
and a C++ pointer where a reference is optional (using nullptr as the
"missing" state).
void foo(bar const& mandatory);
void foo(bar const* optional);
However, there are a few shortcomings to this approach:
1. It lacks semantics. The meaning of a raw pointer is overloaded; are
we passing a reference, an array, an "iterator", an "owning" pointer, or a
string (in the case of char const*)? Even if we know it is a reference,
is it really optional? Some people don't like representing references using
C++ references, so they used pointers that cannot be null instead. The GSL
is addressing this somewhat with the gsl::not_null annotation, but this
is hardly optimal. Speaking of references, they also have overloaded
meaning: passing by reference can be for performance, or because a
reference is genuinely needed (and maybe stored somewhere).
2. C++ references cannot be reassigned. I understand this is one of the
motivating forces behind the operator dot proposal. I agree it is a big
problem.
What we need are some wrapper types which can represent references
(mandatory or optional) to arbitrary objects. One proposal
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4282> already
exists which kind of addresses this need: std::observer_ptr (a.k.a. The
World's Dumbest Smart Pointer). This is nice because it conveys meaning (it
"observes"; it doesn't "own"), but it falls short in a few other areas.
Firstly, it doesn't support mandatory references (pointers can be null).
Secondly, it still has "pointer" semantics. I would like my smart
references to have "reference" semantics; in other words, I do not want to
have to enter the realm of C++ pointers; I do not want to have to do this:
std::observer_ptr<int> o = &i; // address-of operator: yuck!
The problem is that std::observer_ptr is an "observing" pointer modelled on
the "owning" smart pointers, hence why is is kind of dumb. I propose that
we need wrappers designed from the ground up to model some kind of
"reference" concept. However, since std::ref is already taken, and for
other reasons which should become apparent later, I suggest not using the
term "reference". We could follow std::observer_ptr and use the term
"observer", but I feel that the name "view" is more appropriate, having
been coined by std::string_view as a term for something which is a
non-owning reference to another object. Thus, I suggest std::view:
std::view<int> v = i; // no pointer semantics: yay!
But, I hear you say, don't we need the operator dot proposal to get our
"reference-y" semantics? Well, to get C++ reference semantics, yes we do.
However, it is not required. Consider this:
std::view<int> v = i;
v = j; // reassignment of the view
*v = 42; // assignment of the referenced object
This has the advantage that assignment of the view and assignment of the
referenced object are clearly disambiguated; there is no need to change the
language, and there is less potential for confusing. But, but, I hear you
protest, this is pointer semantics! Indeed, the operator dot proposal
mentions this as a motivating factor. However, there is already precedent
for non-pointer usage of operator* and operator-> in std::optional:
std::optional<int> o = i;
o = j; // reassignment of the optional
*o = 42; // assignment of the underlying object
Indeed, std::view would have the exact same semantics as std::optional.
Thus, we have a "smart reference" type which requires no change to the
language, has no null/missing state, supports reassignment, has non-pointer
semantics, and has usage patterns consistent with other types already in
the standard library. Optional references can also be implemented naturally:
std::optional_view<int> o; // can be default constructed
o = i; // reassignment of the optional
o = std::view<int>(j); // optional views can be assigned from views, but
not vice-versa
if (o) {
*o = 42; // assignment of the underlying object
}
o = std::nullopt; // reuse of existing library features: yay!
I think this is pretty neat. And none of this required support for
overloading operator dot. Indeed, I feel that the the ambiguous nature of
operator= for a type which overloads operator dot may be a red flag that
something is amiss. However, I'm sure this has been discussed at length,
and perhaps the operator dot proposal is orthogonal to what I am suggesting
here. I just wanted to show that we can already address a number of
concerns without adding complexity to the language, and in my opinion, far
more cleanly than any solution involving operator dot.
One final thought: would the existence of std::optional_view make
std::observer_ptr redundant? My feeling is "perhaps", but I wouldn't be
surprised the answer were "no".
Thanks for taking the time to read my musings. Please let me know your
opinions, and if anything like this has been suggested before.
Kind regards,
Joseph Thomson
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/41be9d41-d160-4be8-bd1c-a662e108211d%40isocpp.org.
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/41be9d41-d160-4be8-bd1c-a662e108211d%40isocpp.org.