Pizer’s Weblog

programming, DSP, math

C++0x: Interaction between rvalue references & concepts

with 2 comments

Last update: 2008-12-08

I noticed some unfortunate gotchas when using two of the upcoming C++ features together: Rvalue references and concepts/templates. One of these problems can actually be seen in the currend draft of the future C++ specification. As far as I can tell, it looks like a subtle but nasty bug. Check out chapter 23.2.6 [vector, page 826]:

   requires AllocatableElement<Alloc, T, const T&>
   void push_back(const T& x);

   requires AllocatableElement<Alloc, T, T&&>
   void push_back(T&& x);

Suppose, T is regular and has a move-constructor. Then, everything is fine. Depending on the kind of argument (lvalue or rvalue) the appropriate function is selected from the overload set. For lvalues the first function is selected which will store a copy of the argument in the vector. For rvalues the second function is selected which move-constructs a new vector element from the argument.

But what happens when our type T is only move-constructible? In that case the overload set will only contain the second function because of the requires clause of the first one that doesn’t hold. Having only one function in the overload set that takes an rvalue reference is fatal in this case because rvalue references can bind to lvalues as well. In order to tell whether the argument was an lvalue or an rvalue we need function overloading on the type of references. So, what happens with types that are not copy-constructible? The 2nd function will be invoked even in case of lvalue arguments because it’s the only candidate. Those lvalue arguments will possibly be destroyed because they are used as source for a move-construction.

This is one example where the combination of concepts and rvalue references is error-prone. The only solution I can think of is to add a third function declaration a la

  requires !AllocatableElement<Alloc, T, const T&>
  void push_back(const T&) = delete;

So, to get this thing right we need three push_back declarations. One that is only selected when the argument was an rvalue and two for the lvalue case. The deleted version is necessary to avoid binding lvalues to rvalue references. The deleted function will be part of the overload set in case of non-copyable types so that any attempt to add an lvalue object to a vector will be ill-formed.

[ Update: Unfortunately, the suggested solution doesn’t work in conceptg++ (svn727). I wonder whether it is even well-formed with respect to the current draft. ]

Another gotcha is the following: Mixing move semantics with generic code may lead to surprizes:

  template<class T> void foo(T const& x); // #1
  template<class T> void foo(T && x);     // #2

In this case #2 is always invoked for non-const objects since the type parameter T might be deduced to be an lvalue reference. This is due to the template type parameter deduction rules. If you write “T &&” and T needs to be deduced by the compiler it will make T an lvalue reference in case the function’s argument was an lvalue. This allows automatic detection of lvalues and rvalues and is required for perfect forwarding of function parameters. But in our case we need to circumvent this special rule so that T never becomes a reference type. One solution is to employ SFINAE:

  template<class T, class R>
    struct disable_if_lreference { typedef R type };
  template<class T, class R>
    struct disable_if_lreference<T&,R> {};

  template<class T>
  typename disable_if_lreference<T,void>::type
  foo(T && x);     // #2.b  (new version)

By using SFINAE the second function won’t be part of the overload resolution set when T is an lvalue reference because disable_if_lreference has no typedef for ‘type’. Consequently #1 will be the only candidate and invoked for lvalue arguments.

There’s a third case that may produce headaches:

  concept C<typename T> {}
  concept_map C<int> {}

  template<C T>
  void do_something(T && x);

  int main() {
    int k=42;
    do_something(23); // OK
    do_something(k); // FAILS since T=int& and !C<int&>
  }

This is something programmers should be aware of. I have no solution yet. Only an idea:

  auto concept ValueTypeOf<typename T> {
    typename type = T;
    requires std::Convertible<T,type>
  }
  template<typename T> concept_map ValueTypeOf<T&> {
    typedef T type;
  }

  template<ValueTypeOf T>
    requires C<ValueTypeOf<T>::type>
  void do_something(T && x);

But I’m not sure if that’s going to work. I still need to check that …

Update: Yes, it seems to work at least with conceptg++ (svn727). In the above example the concept C is empty, so I added a requirement to check if it’s really working like intended:

  concept C<typename T> {
    void foobar(T);
  }

  void foobar(int x) {}

  concept_map C<int> {}

  template<ValueTypeOf T>
    requires C<ValueTypeOf<T>::type>
  void do_something(T && x) {
    foobar(x);
  }

  int main() {
    int k=42;
    do_something(23); // OK, T=int
    do_something(k); // OK, T=int&, Convertible<T,int>, C<int>
  }

So long!
– P

About these ads

Written by pizer

December 6, 2008 at 6:44 pm

2 Responses

Subscribe to comments with RSS.

  1. Hi.

    Cool post, nice catch. Why do you believe:

    requires !AllocatableElement
    void push_back(const T&) = delete;

    to not be well-formed?

    cfeckardt

    April 14, 2009 at 1:34 am

  2. Hi! Thanks for the comment!

    The experimental compiler “ConceptGCC” didn’t accept this kind of overload. As far as I can tell it is legal since the requires clauses are different. Recently, the problem has been fixed by changing the language, though: See N2812 and N2831.

    pizer

    April 14, 2009 at 7:13 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: