This will perform 1 copy construction and 3 move constructions.
- Make a copy of 
a to bind to lhs. 
- Move construct 
lhs out of the first +. 
- The return of the first 
+ will bind to the by value lhs parameter of the second + with elision. 
- The return of the second 
lhs will incur the second move construction. 
- The return of the third 
lhs will incur the third move construction. 
- The temporary returned from the third 
+ will be constructed at sum. 
For each of the move constructions described above, there is another move construction that is optionally elided.  So you are only guaranteed to have 1 copy and 6 moves.  But in practice, unless you -fno-elide-constructors, you will have 1 copy and 3 moves.
If you don't reference a after this expression, you could further optimize with:
X sum = std::move(a) + b + c + d;
resulting in 0 copies and 4 moves (7 moves with -fno-elide-constructors).
The above results have been confirmed with an X which has instrumented copy and move constructors.
Update
If you're interested in different ways to optimize this, you could start with overload the lhs on X const& and X&&:
friend X operator+(X&& lhs, X const& rhs) {
    lhs += rhs;
    return std::move(lhs);
}
friend X operator+(X const& lhs, X const& rhs) {
    auto temp = lhs;
    temp += rhs;
    return temp;
}
This gets things down to 1 copy and 2 moves.  If you are willing to restrict your clients from ever catching the return of your + by reference, then you can return X&& from one of the overloads like this:
friend X&& operator+(X&& lhs, X const& rhs) {
    lhs += rhs;
    return std::move(lhs);
}
friend X operator+(X const& lhs, X const& rhs) {
    auto temp = lhs;
    temp += rhs;
    return temp;
}
Getting you down to 1 copy and 1 move.  Note that in this latest design, if you client ever does this:
X&& x = a + b + c;
then x is a dangling reference (which is why std::string does not do this).