Example(/* appropriate parameters */);
join(Example const &);
This makes split() a factory function, of sorts. It is still a function that modifies its argument, but at least it isn't a constructor and the name clearly parallels that of join().
I'm sure you're concerned about backward compatiblity. You should be able to use template metaprogramming to determine whether the object given to parallel_reduce() is of a type that provides a static member function named split() or a splitting constructor while deprecating the latter.
We considered this alternative while designing the class, but settled on the spitting constructor for efficiency reasons. Witha split() factory function, you would need an additional copy constructor call.
The range objects also share the "splittable" concept. In that case, splitting occurs frequently and efficiency is critical. We used a splitting constructor to avoid theoverhead of an additional copy constructor call at each split.
We've tried hard to provide a user-friendingly interface while still providing efficient execution. While it can be argued that the splitting constructor is a bit odd, we believe you'll get used to it quickly and be happy with the efficiency.
We can always provide a second method to do this if users demand it after some use. At this point, we think it is important to have a single, efficient way to do this.
[I was out enjoying an 8 week sabbatical.]
Yes, we considered a variety of interfaces, and as the former lead developer of KAI C++ I knewabout copy elision and return-slot optimization in C++ compilers. The splitting constructor approach seems to be the simplest for users to write efficiently without knowing the subtle points of catering to compiler's whims about what it can and cannot handle with respect to copy elision and return-slot optimization.