N2857: issues with concept reqruiements and references

6 views
Skip to first unread message

SG

unread,
May 13, 2009, 12:19:48 PM5/13/09
to
Hi!


I noticed a couple of errors in the library section of N2857 regarding
concepts and references I'd like to discuss here.


1. RvalueOf

The RvalueOf concept is supposed to expose an associated type that is
an rvalue reference (type transformation):

concept RvalueOf<typename T> {
typename type = T&&;
requires ExplicitlyConvertible<T&,type>
&& Convertible<T&&,type>;
}

template<typename T> concept_map RvalueOf<T&> {
typedef T&& type;
}

The concept_map is however useless/ill-formed because it doesn't
satisfy the 2nd associated requirement: Convertible<T&&,type>. It
might not be obvious at first but here are two different Ts involved.
T in the concept_map is a non-reference. The concept_map is for an
lvalue reference (RvalueOf<T&>) so "T&&" in the associated requirement
is still an lvalue reference due to reference collapsing rules. But an
lvalue reference is not implicitly convertible to an rvalue reference
==> This concept_map doesn't satisfy all requirements.


2. The deal with MoveConstructible, CopyConstructible and references.

Throughout the library MoveConstructible and CopyConstructible is used
to constrain types of function arguments/parameters where the non-
conceptized version would also allow the use of references.
Unfortunately lvalue references don't satisfy MoveConstructible. Since
CopyConstructible is a refinement of MoveConstructible an lvalue
reference also doesn't satisfy CopyConstructible. Why? Well,
MoveConstructible<T> has an associated requirement
HasConstructor<T,RvalueOf<T>::type>. But an lvalue reference can't be
constructed implicitly from an rvalue reference.

Here's an example from the document (�20.7 [function.objects] page
571):

template<Returnable S, ClassType T, MoveConstructible A>
class mem_fun1_t;

template<Returnable S, ClassType T, MoveConstructible A>
mem_fun1_t<S,T,A> mem_fun(S (T::*f)(A));

Especially when it comes to constraining a function's parameter types
the concept MoveConstructible and CopyConstructible are (over)used but
*overconstrain* the argument types because they don't allow lvalue
references. In my humble opinion these situations call for
requirements Constructible, Convertible or Callable, but not
MoveConstructible. MoveConstructible contains requirements that are
not really useful in this situation. In this case Convertible<A&&,A>
or Constructible<A,A&&> should suffice. Maybe it's a good idea to add
another concept for this:

auto concept Passable<typename A, typename P> : Convertible<P,A> {}

just so that people don't use MoveConstructible to constrain argument/
parameter types that could just as well be lvalue references. It may
make sense to define a weaker concept that MoveConstructible refines
upon:

auto concept Forwardable<typename T> : Constructible<T,T&&> {}


3. The "perfect forwarding / concepts gotcha"

Here's another code example. This time from �20.3.3 [pairs] (N2857,
page 539)

template<MoveConstructible T1, MoveConstructible T2>
pair<V1, V2> make_pair(T1 && x, T2 && y);

What's wrong this that? Well, T1 and/or T2 might be deduced to be an
lvalue reference. The author probably wanted to constrain
remove_reference<T1/T2>::type instead of T1/T2. Since lvalue
references are not MoveConstructible this function template doesn't
work on lvalues (Oups!)

This case is even more complicated by the requirement that T1 and T2
need to be magically transformed to V1 and V2 so that

int i = 2;
make_pair(ref(i),42);

returns an object of type pair<int&,int>. If we ignore this case it
might be solved via

template<StripReference T1, StripReference T2>
requires MoveConstructible<T1::type>
&& MoveConstructible<T2::type>
pair<V1, V2> make_pair(T1 && x, T2 && y);

where StripReference has an associated type "type" that strips the top-
level reference away:

auto concept StripReference<typename T> {
typename type = T;
requires SameType<T&,type&>
&& !Reference<type>;
}

(insert missing concept maps and a definition of Reference here).

Maybe it's even worth thinking about a short cut sytnax that makes the
last make_tuple example with StripReference equivalent to this:

template<MoveConstructible & T1, MoveConstructible & T2>
pair<V1, V2> make_pair(T1 && x, T2 && y);

Note: The ampersand between the concept and the type means that the
requirement applies to the referred type in case the type is a
reference. (just an idea)

Since I mentioned the concept LvalueReference and RvalueReference
already I could just as well comment on them: They don't actually
contain any associated requirements which may keep the compiler in the
dark. For example, why aren't they defined like this:

auto concept Reference<typename T> {
requires SameType<T,T&&>;
}

auto concept LvalueReference<typename T> : Reference<T> {
requires SameType<T,T&>;
}

auto concept RvalueReference<typename T> : Reference<T> {
requires SameType<T,T&&>
&& !LvalueReference<T>;
}


That's all for now! :)

Cheers!
SG


--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]

SG

unread,
May 14, 2009, 7:22:08 PM5/14/09
to
On 13 Mai, 18:19, SG <s.gesem...@gmail.com> wrote:
> 1. RvalueOf
>
> The RvalueOf concept is supposed to expose an associated type that is
> an rvalue reference (type transformation):
>
> auto concept RvalueOf<typename T> {

> typename type = T&&;
> requires ExplicitlyConvertible<T&,type>
> && Convertible<T&&,type>;
> }

Since lvalue references can't satisfy the 2nd requirement without
'type' being an lvalue reference this concept needs some rewriting.
Perhaps something like this:

auto concept RvalueOf<typename T> {
RvalueReference type = T&&;
requires SameType<T&,type&>;
}

template<typename T> concept_map RvalueOf<T&> {
typedef T&& type;
}

> Since I mentioned the concept LvalueReference and RvalueReference


> already I could just as well comment on them: They don't actually
> contain any associated requirements which may keep the compiler in the
> dark. For example, why aren't they defined like this:
>
> auto concept Reference<typename T> {
> requires SameType<T,T&&>;
> }
>
> auto concept LvalueReference<typename T> : Reference<T> {
> requires SameType<T,T&>;
> }
>
> auto concept RvalueReference<typename T> : Reference<T> {
> requires SameType<T,T&&>
> && !LvalueReference<T>;
> }

The last concept contains a requirement that is redundant since it's
already inherited from Reference<T>. So,

auto concept RvalueReference<typename T> : Reference<T> {

requires !LvalueReference<T>;
}

should do the trick.

Greg Herlihy

unread,
Jun 7, 2009, 10:16:11 PM6/7/09
to
On May 13, 9:19 am, SG <s.gesem...@gmail.com> wrote:
>
> I noticed a couple of errors in the library section of N2857 regarding
> concepts and references I'd like to discuss here.
>
> 1. RvalueOf
>
> The RvalueOf concept is supposed to expose an associated type that is
> an rvalue reference (type transformation):
>
> concept RvalueOf<typename T> {
> typename type = T&&;
> requires ExplicitlyConvertible<T&,type>
> && Convertible<T&&,type>;
> }
>
> template<typename T> concept_map RvalueOf<T&> {
> typedef T&& type;
> }
>
> The concept_map is however useless/ill-formed because it doesn't
> satisfy the 2nd associated requirement: Convertible<T&&,type>. It
> might not be obvious at first but here are two different Ts involved.

More precisely, the RvalueOf<T&> concept_map is a effectively a
template "specialization" for lvalue reference type arguments.

> T in the concept_map is a non-reference. The concept_map is for an
> lvalue reference (RvalueOf<T&>) so "T&&" in the associated requirement
> is still an lvalue reference due to reference collapsing rules.

No, there is no collapsing. This concept map is no different than any
other specialized template. Therefore, if the concept map's type
argument matches the parameterized type "T&" - then the type argument
must be an lvalue reference -to- a type "T". In other words, "T&" is
the lvalue reference type - while "T" by itself is the type being
referenced. In short, the lvalue reference specialization "strips
away" the reference portion of the type argument and then uses the
recovered base type to synthesize the needed rvalue reference type.

To take a speicific example: if RValueOf's type argument is "int&",
then the "T" in the T& specialization would correspond to "int" and
the RvalueOf<int&>::type typedef would correspond to "int&&" - which
is exactly the rvalue reference type needed to satisfy the concept's
requirements.

> lvalue reference is not implicitly convertible to an rvalue reference
> ==> This concept_map doesn't satisfy all requirements.

As explained above, the RvalueOf<T&> concept map synthesizes the
correct rvalue reference type directly from the lvalue reference type
argument - and does not rely on any kind of type conversion from one
reference type to the other.

> 2. The deal with MoveConstructible, CopyConstructible and references.

> ...


>
> Especially when it comes to constraining a function's parameter types
> the concept MoveConstructible and CopyConstructible are (over)used but
> *overconstrain* the argument types because they don't allow lvalue
> references.

As I noted in another post, lvalue references are MoveConstructible.
So a MoveConstructible parameter type in a function definition - would
not necessarily prohibit an lvalue reference from being passed as a
corresponding argument.

> auto concept Forwardable<typename T> : Constructible<T,T&&> {}
>
> 3. The "perfect forwarding / concepts gotcha"
>
> Here's another code example. This time from �20.3.3 [pairs] (N2857,
> page 539)
>
> template<MoveConstructible T1, MoveConstructible T2>
> pair<V1, V2> make_pair(T1 && x, T2 && y);
>
> What's wrong this that? Well, T1 and/or T2 might be deduced to be an
> lvalue reference.

Not possible - the compiler must deduce the "T1" portion of a "T1&&"
type. Clearly, "T1&&" is an rvalue reference type (with T1 being the
referenced type). Now, because a reference type cannot refer to any
other kind of reference type as its base (there are no references-to-
references) - the compiler must deduce that T1 is a non-reference type
(which is just as well - since std::pair does not allow instantiation
with any kind of reference type).

>
> This case is even more complicated by the requirement that T1 and T2
> need to be magically transformed to V1 and V2 so that
>
> int i = 2;
> make_pair(ref(i),42);
>
> returns an object of type pair<int&,int>.

No. Since std::pair requires two CopyConstructible types, the make_pair
() in the above example will return a std::pair<int, int> object.

> Since I mentioned the concept LvalueReference and RvalueReference
> already I could just as well comment on them: They don't actually
> contain any associated requirements which may keep the compiler in the
> dark.

> For example, why aren't they defined like this:
>
> auto concept Reference<typename T> {
> requires SameType<T,T&&>;
> }
>
> auto concept LvalueReference<typename T> : Reference<T> {
> requires SameType<T,T&>;
> }
>
> auto concept RvalueReference<typename T> : Reference<T> {
> requires SameType<T,T&&>
> && !LvalueReference<T>;
> }

If defined as proposed above, the LvalueReference and RvalueReference
concepts could no longer be used to create specialized concept maps
that apply only to reference types. In short, there would be no reason
left to declare these two concepts in the first place.

Greg

SG

unread,
Jun 8, 2009, 11:35:49 AM6/8/09
to
On 8 Jun., 04:16, Greg Herlihy <gre...@mac.com> wrote:
> On May 13, 9:19 am, SG <s.gesem...@gmail.com> wrote:
> > I noticed a couple of errors in the library section of N2857 regarding
> > concepts and references I'd like to discuss here.
>
> > 1. RvalueOf
>
> > The RvalueOf concept is supposed to expose an associated type that is
> > an rvalue reference (type transformation):
>
> > concept RvalueOf<typename T> {
> > typename type = T&&;
> > requires ExplicitlyConvertible<T&,type>
> > && Convertible<T&&,type>;
> > }
>
> > template<typename T> concept_map RvalueOf<T&> {
> > typedef T&& type;
> > }
>
> > The concept_map is however useless/ill-formed because it doesn't
> > satisfy the 2nd associated requirement: Convertible<T&&,type>. It
> > might not be obvious at first but here are two different Ts involved.
>
> More precisely, the RvalueOf<T&> concept_map is a effectively a
> template "specialization" for lvalue reference type arguments.

So? It still needs to satisfy all the requirements of the concept. In
this case it does not. It does not satisfy Convertible<T&&,type>.

> > T in the concept_map is a non-reference. The concept_map is for an
> > lvalue reference (RvalueOf<T&>) so "T&&" in the associated requirement
> > is still an lvalue reference due to reference collapsing rules.
>
> No, there is no collapsing. This concept map is no different than any
> other specialized template. Therefore, if the concept map's type
> argument matches the parameterized type "T&" - then the type argument
> must be an lvalue reference -to- a type "T". In other words, "T&" is
> the lvalue reference type - while "T" by itself is the type being
> referenced.

Yes, I'm aware of that. But this is not the issue. You seem to confuse
both Ts. Let me rewrite it to make it a little clearer:

concept RvalueOf<typename T> {
typename type = T&&;

requires ExplicitlyConvertible<T&,type> // #1
&& Convertible<T&&,type>; // #2
}

template<typename U> concept_map RvalueOf<U&> { // #3
typedef U&& type;
}

I have replaced T with U for the concept map. Let's check whether
T=int& satisfies the concept. Concept map lookup leads to the concept
map for lvalue references (#3) with T=U&, U=int. What requirements
does it have to satisfy?

#1 : ExplicitlyConvertible<T&,type>
<=> ExplicitlyConvertible<U&,U&&> due to T=U&, type=U&&
<=> ExplicitlyConvertible<int&,int&&> due to U=int
#2 : Convertible<T&&,type>
<=> Convertible<U&,U&&> due to (T=U&, type=U&&)
<=> Convertible<int&,int&&> due to U=int

Note that the first requirement is satisfied but the 2nd is not. It's
not satisfied since N2831. The concept_map isn't to blame here
(although it's ill-formed). It's the concept that is broken because it
doesn't allow lvalue references to satisfy the concept anymore.

> [...]

> > 3. The "perfect forwarding / concepts gotcha"
>
> > Here's another code example. This time from �20.3.3 [pairs] (N2857,
> > page 539)
>
> > template<MoveConstructible T1, MoveConstructible T2>
> > pair<V1, V2> make_pair(T1 && x, T2 && y);
>
> > What's wrong this that? Well, T1 and/or T2 might be deduced to be an
> > lvalue reference.
>
> Not possible - the compiler must deduce the "T1" portion of a "T1&&"
> type. Clearly, "T1&&" is an rvalue reference type (with T1 being the
> referenced type). Now, because a reference type cannot refer to any
> other kind of reference type as its base (there are no references-to-
> references) - the compiler must deduce that T1 is a non-reference type
> (which is just as well - since std::pair does not allow instantiation
> with any kind of reference type).

You don't seem to be aware of the template parameter deduction rules
that make perfect forwarding possible. The following example won't
work with "current C++0x":

template<MoveConstructible T1, MoveConstructible T2>

void foo(T1 && x, T2 && y);

int main() {
int a = 23;
int b = 42;
foo(a,b);
}

The compiler will deduce T1 and T2 to be int& because the parameters
'a' and 'b' are lvalues. But as shown above, lvalue references don't
satisfy MoveConstructible. So, foo is not usable on lvalues. The
author probably wanted to constrain remove_reference<T1>::type instead
of T1 which is why I called it the "perfect forwarding / concepts"
gotcha. Solution:

template<RemoveReference T1, RemoveReference T2>


requires MoveConstructible<T1::type>
&& MoveConstructible<T2::type>

void foo(T1 && x, T2 && y);

with RemoveReference exposing an associated type that strips away the
reference:

auto concept RemoveReference<typename T> {
typename type = T;
requires !Reference<type> && SameType<T&,type&>;
}

// insert concept maps for lvalue and rvalue references

Unfortunately, the declaraion of foo gets rather verbose.

> > Since I mentioned the concept LvalueReference and RvalueReference
> > already I could just as well comment on them: They don't actually
> > contain any associated requirements which may keep the compiler in the
> > dark.
> > For example, why aren't they defined like this:
>
> > auto concept Reference<typename T> {
> > requires SameType<T,T&&>;
> > }
>
> > auto concept LvalueReference<typename T> : Reference<T> {
> > requires SameType<T,T&>;
> > }
>
> > auto concept RvalueReference<typename T> : Reference<T> {

> > requires !LvalueReference<T>;


> > }
>
> If defined as proposed above, the LvalueReference and RvalueReference
> concepts could no longer be used to create specialized concept maps
> that apply only to reference types. In short, there would be no reason
> left to declare these two concepts in the first place.

That's intentional. The current draft does not allow users to provide
concept_maps for these ("A program shall not provide concept maps for
any concept in this section."). What you seem to have in mind is
something like a concept "ReferenceLike". It's similar to
FloatingPointType and FloatingPointLike. The first is only satisfied
by built-in floating point types. The 2nd one is more general and
users may provide concept maps for the 2nd concept.


Cheers!
SG

Reply all
Reply to author
Forward
0 new messages