Lambda + std::move + Array Notation

Here is an interesting example of combining new language features.

I needed to write a routine similar to std::copy, except that the routine needed to destroy its source sequence; i.e., revert it to raw memory.  I was amused that I could exploit both C++ 2011 and the Intel Array Notation extension to give the compiler wider license to optimize the code.   Here is the code: 

template<typename T>

T* destructive_move( T* first, T* last, T* output ) {

    size_t n = last-first;

    []( T& in, T& out ){

        out = std::move(in);

        in.~T();

    }( first[0:n], output[0:n] );

    return output+n;

}


I used the Intel 12.0 compiler and Microsoft Visual Studio 2010 to compile it.  The code combines three new features:

    • A C++11 lambda expression:  That's the code introduced by  [], and creates a functor object.  The functor has two formal parameters in and out.

    • A C++11 "move assignment": It moves the contents of in to out, and gives license to operator= to mangle the value of in for sake of optimization.   This can be a significant performance advantage over a C++98 copy assignment  "out = in".  For example, if T is a std::vector<U> with N elements, a "copy assignment" of T takes O(N) time, but "move assignment" takes only O(1) time, because it can steal the underlying hunk of N elements instead of copying them.  Furthermore, often "move assignment" can be guaranteed to not throw an exception, because it steals resources instead of allocating more resources that might run out.

    • Intel®  Cilk PlusArray Notation: It maps the functor over first[0:n] and output[0:n].  Each of these represents a sequence of n elements, starting at addresses first and output respectively.  Array Notation is an Intel extension that avoids the need for an explicit loop, and gives the compiler license to vectorize the code.   The only drawback is that if an exception is thrown, it's not clear how many elements of in[o:n] were actually destroyed.  Though for the cases of interest to me, neither the "move assignment" nor explicit destructor ~T() can throw an exception. 



I could have written the routine as:

template<typename T>

T* destructive_move( T* first, T* last, T* output ) {

    size_t n = last-first;

    output[0:n] = std::move(first[0:n]);

    first[0:n].~T();

    return output+n;

}


which is more concise, but has the drawback of making two sweeps over memory.

More about Cilk Plus can be found here.

For more complete information about compiler optimizations, see our Optimization Notice.

1 comment

Top
anonymous's picture

Elegant. Invoking the lambda directly after declaring it is great, but I didn't catch it on the first read. I've always appreciated how lambdas can be used to hide implementation detail in a class or function template, this is a case where it is also efficient :).

Add a Comment

Have a technical question? Visit our forums. Have site or software product issues? Contact support.