Let's say we just wrap std::string and do a simplified version of operator+:
struct C { 
    std::string val;
    C&& operator+(const C& rhs) && {
        val += rhs.val;
        return std::move(*this);
    }   
    std::string::iterator begin() { return val.begin(); }
    std::string::iterator end() { return val.end(); }
};
With that, this works fine:
for (char c : C{"hello"}) { .. }
the range-for expression will extend the lifetime of the temporary, so we're ok. However, consider this:
for (char c : C{"hello"} + C{"goodbye"}) { .. }
We effectively have:
auto&& __range = C{"hello"}.operator+(C{"goodbye"});
Here, we're not binding a temporary to a reference. We're binding a reference. The object doesn't get its lifetime extended because... it's not an object. So we have a dangling reference, and undefined behavior. This would be very surprising to users who would expect this to work:
for (char c : std::string{"hello"} + std::string{"goodbye"}) { .. }
You'd have to return a value:
C operator+(const C& rhs) && {
    val += rhs.val;
    return std::move(*this);
}
That solves this issue (as now we have temporary extension), and if moving your objects is cheaper than copying them, this is a win.