A workaround could be to use std::tuple<> for the return type. (?)
It's not a matter of workaround, but rather the point of Monads. Using tuple is an implementation detail.
Call f and h functions with type signature (double -> double). You have demonstrated how to compose f and h. From what I understand, this is a specific subset of the application of Monads, it does not use their full power.
But suppose f and h are each functions with signature (double -> (double, int)). How do you compose them? The bind operator of a Monad does exactly this, typically it will thread the double arg through, but tells exactly how the ints will be combined between f and h (logical and? or? something else? your choice, each one yields a different Monad).
So, f >> h actually means, change h from (double -> (double, int)) to ((double, int) -> (double, int)), and compose the resulting function with f.
However, it goes further in that the "primary" types (double in this case) don't have to match between f and h, so long as bind provides the proper transformation. Eg, we could have (double -> (double, int)) and (double -> (str, int)).
In this context, the return operator does (double -> (double, int)) so that the value can be fed into the Monadic chain. A full chain would look like
g = f >> h
(return 10.0) >> g [yields some result]