-
Notifications
You must be signed in to change notification settings - Fork 8
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
Assignable concept looks wrong #229
Comments
OK, coming back to this. For
The "Let |
Also, the application of The poor wording for |
|
What does "an object |
What if |
The intent is that
|
What if
So proxy references cannot satisfy |
We've avoided characterizing proxy references in the design by placing requirements on expressions involving proxy iterators instead. We have |
... and |
Almost necessarily so: if we could define |
What about this from above?
What if |
If it's a prvalue, it doesn't refer to an object, and we can't reasonably talk about mutation of a non-object. |
But the fact that you asked for clarification tells me that we need a note. |
Tell me more. Because the more I think about #226, the more it seems to me that we need some sort of characterization of "reference-like" types. What problems do you see? |
Characterizing reference-like types: Strawman Approach 1:"A techterm{reference-like} type is a class type such that objects of that type have values that are comprised, in whole or in part, from parts outside the object's storage [or a reference to such?]." Strawman Approach 2:"A techterm{reference-like} type is a class type whose copy constructor is not equality preserving because (copies do not have independent values|values may not be stable) [, or a reference to such class type?]." Or something. We may also need to say something about an object's "value", since it doesn't seem to be characterized anywhere. Loosely in this context it should mean any property of the object that may be observed, either directly or indirectly. We would want to give examples of reference-like types, such as OK, rip it apart. |
Ping. @CaseyCarter? |
Recall that the original problem we're trying to solve here is how to characterize when the assignment expression in Proxy references actually break that definition in two ways:
The proposal I made in the other thread (add an The problem this thread seems to be about is how to devise a complete characterization of proxy references so we can describe their behavior in equality preserving expressions. Notably, a solution to this problem will almost trivially provide a solution to the I don't see how either of the two "Strawman approaches" presented herein get us closer to solutions to any of the above problems. I don't see how approach #1 differs from a non-reference-like type: we don't care where things are stored, the only "values" we care about are the results of evaluating equality-preserving expressions. Approach #2 seems to say "reference-like types are class types that don't participate in the semantics of equality-preserving expressions." which is fine, but doesn't tell me how I can reason about these things. My intuition is that the way to break these problems open is to somehow hijack the mapping from "names" to "values" in equality preserving expressions. Given the definitions: int i = 42;
int &j = i;
Similarly: int i = 42;
reference_wrapper<int> k = i;
We've never really locked down what the "operands" of an expression are. I propose that we should do so, and in such way that in: int i = 42;
reference_wrapper<int> k = i;
k + k;
k.get() + k.get(); both expressions k + k; // #1
int j = 11;
k = j;
k + k; // #2 #1 and #2 appear to be equivalent expressions, but we cannot expect them to preserve equality since the name |
There is a related issue wrt proxy references in that none of the transformation metafunctions (add/remove reference, add/remove const) works for them. If we had a generic way to "reach in" to a proxy reference and do these kinds of transformations, then we have a powerful mechanism that would be generally useful. It could also solve the problems you note above: we reach in and strip top-level cv and ref qualifiers to get the "value type", and then require that the proxy reference is convertible to the value. |
And to finish the thought, I propose: template <class T, template <class> class F>
struct transform_proxy
: transform_proxy<remove_cv_t<T>, F>
{};
template <class T, template <class> class F>
requires Same<T, remove_cv_t<T>>()
struct transform_proxy<T, F>
{
using not_specialized = unspecified;
};
template <class T, template <class> class F>
using transform_proxy_t = typename transform_proxy<T,F>::type;
template <class T>
struct is_proxy : true_type {};
template <class T>
requires requires {typename transform_proxy_t<T, remove_reference_t>::not_specialized;}
struct is_proxy : false_type {};
template <class T>
struct is_reference_like : disjunction<is_reference<T>, is_proxy<T>>
{}; By default, it is specialized for types such as template <class T, template <class> class F>
struct transform_proxy< reference_wrapper<T>, F>
{
using type = reference_wrapper< F<T> >;
};
template <class T>
struct transform_proxy< reference_wrapper<T>, remove_reference_t >
{
using type = T;
}; Then we can infer the value type with We can handle the mapping of name to value by coercing a proxy to the value type. But can we do this without forcing a copy? EDIT: It's not hard to poke holes in this, but perhaps there is the germ of an idea here. EDIT 2: All this garbage is what I hoped to avoid with |
Proxies aside, can we get to some suggested wording that is better than what we have? Is this comment good enough? |
From the Issaquah review: "Assignable should require a non-const lvalue LHS." Sentiment in the room was that if we are not yet ready to define semantics for assigning to something that isn't a modifiable lvalue we should restrict the domain of the concept instead of only applying semantics to the restricted domain. #229 (comment) is better than what we have, but still needs more work. |
And FWIW, I agree that we should fix the wording to support the current intended design, close this, and move discussion about making |
|
Proposed Resolution:Change [concepts.lib.corelang.assignable] as follows: template <class T, class U>
concept bool Assignable() {
- return CommonReference<const T&, const U&>() && requires(T&& t, U&& u) {
- { std::forward<T>(t) = std::forward<U>(u) } -> Same<T&>;
- };
+ return std::is_lvalue_reference<T>::value &&
+ Same<std::remove_reference_t<T>, std::remove_cv_t<std::remove_reference_t<T>>>() &&
+ CommonReference<T, const U&>() &&
+ requires(T t, U&& u) {
+ { t = std::forward<U>(u); } -> Same<T>;
+ };
}
-1 Let t be an lvalue of type T, and R be the type remove_reference_t<U>. If U is an
- lvalue reference type, let v be an lvalue of type R; otherwise, let v be an rvalue
- of type R. Let uu be a distinct object of type R such that uu is equal to v.
+1 Let `t` be an lvalue which refers to an object `o` such that `decltype((t))` is `T`,
+ and `u` an expression such that `decltype((u))` is `U`. Let `u2` be a distinct object that is
+ equal to `u`.
Then Assignable<T, U>() is satisfied if and only if
-(1.1) — std::addressof(t = v) == std::addressof(t).
+(1.1) — std::addressof(t = u) == std::addressof(o).
-(1.2) — After evaluating t = v:
+(1.2) — After evaluating t = u:
-(1.2.1) — t is equal to uu.
+(1.2.1) — t is equal to u2.
-(1.2.2) — If v is a non-const rvalue, its resulting state is unspecified. [Note: v must still
- meet the requirements of the library component that is using it. The operations listed
- in those requirements must work as specified. —end note ]
+(1.2.2) — If u is a non-const xvalue, the resulting state of the object to which it refers is
+ unspecified. [ Note: the object must still meet the requirements of the library
+ component that is using it. The operations listed in those requirements must work as
+ specified. —end note ]
-(1.2.3) — Otherwise, v is not modified.
+(1.2.3) — Otherwise, if u is a glvalue, the object to which it refers is not modified. |
Should this program be ill-formed (it currently is as of CaseyCarter/cmcstl2@6ec9162)? template <class T, class U>
requires
std::experimental::ranges::Assignable<T, U>()
void foo(T, U) {}
int main()
{
foo(std::pair<int, int>{}, std::pair<int, double>{});
} |
Yes. It's testing rvalues for assignment. You probably want to change this to: template <class T, class U>
requires
std::experimental::ranges::Assignable<T&, U>()
void foo(T t, U u) { } |
Whoops, that's exactly what I meant! Even so, it's not assignable on account of cmcstl2/include/stl2/type_traits.hpp:187:15: note: within ‘template<class T, class U> concept bool std::experimental::ranges::v1::CommonReference() [with T = std::pair<int, int>&; U = const std::pair<int, double>&]’
concept bool CommonReference() {
^~~~~~~~~~~~~~~
cmcstl2/include/stl2/type_traits.hpp:187:15: note: ‘std::experimental::ranges::v1::models::CommonReference’ evaluated to false |
The |
|
Unless restricted to the common domain of values representable by both types, of course. |
I'm beginning to question whether the |
For For |
...and the assignment operations of |
My point is that this: Case 1T t(a); should be the same as: Case 2T t;
t = a; The concept we have to constrain case 2 enforces equational reasoning, but we don't have any such guarantee for case 1. I think it's fishy. |
There are too many types in C++ for which the value of the constructed object is not a function of the values of the constructor parameters. Timers are the first and most obvious example. We could not specify the standard library without a "weak" |
* P0541 * P0547 * P0579 * ericniebler/stl2#155 * ericniebler/stl2#167 * ericniebler/stl2#172 * ericniebler/stl2#229 * ericniebler/stl2#232 * ericniebler/stl2#239 * ericniebler/stl2#241 * ericniebler/stl2#242 * ericniebler/stl2#243 * ericniebler/stl2#244 * ericniebler/stl2#245 * ericniebler/stl2#255 * ericniebler/stl2#286 * ericniebler/stl2#299 * ericniebler/stl2#301 * ericniebler/stl2#310 * ericniebler/stl2#311 * ericniebler/stl2#313 * ericniebler/stl2#322 * ericniebler/stl2#339 * ericniebler/stl2#381 Remove post-increment experiment in `move_iterator`. Remove `EqualityComparable`/`Sentinel<default_sentinel>` extensions to `ostreambuf_iterator`.
* P0541 * P0547 * P0579 * ericniebler/stl2#155 * ericniebler/stl2#167 * ericniebler/stl2#172 * ericniebler/stl2#229 * ericniebler/stl2#232 * ericniebler/stl2#239 * ericniebler/stl2#241 * ericniebler/stl2#242 * ericniebler/stl2#243 * ericniebler/stl2#244 * ericniebler/stl2#245 * ericniebler/stl2#255 * ericniebler/stl2#286 * ericniebler/stl2#299 * ericniebler/stl2#301 * ericniebler/stl2#310 * ericniebler/stl2#311 * ericniebler/stl2#313 * ericniebler/stl2#322 * ericniebler/stl2#339 * ericniebler/stl2#381 Remove post-increment experiment in `move_iterator`. Remove `EqualityComparable`/`Sentinel<default_sentinel>` extensions to `ostreambuf_iterator`.
Looks like it's half-way between an old-style object concept and a less semantically meaningfulSwappable
-like concept. Which should it be? (I'll flesh this issue out later.)For
Assignable
we have:The "Let
t
be an lvalue of typeT
" is at odds with the concept definition. It needs the lvalue/rvalue dance.Also, the application of
==
to entities whose types aren't constrained to satisfyEqualityComparable
is meaningless;uu == v
andt == uu
should use "is equal to."Proposed Resolution:
Adopt P0547R1.
The text was updated successfully, but these errors were encountered: