Given
- a
makeDocument
function creating a temporary resource and handing it over viastd::share_ptr
, - a
deref
function that encapsulates the application of the default*
operator, - and a consumer
print
function, boost::hana::compose
I've noticed that
compose(print, deref, makeDocument)("good"); // OK
compose(print, compose(deref, makeDocument))("bad"); // UB
and I'd like to understand why. Specifically, why does the latter expression result in the temporary std::shared_ptr
handed to deref
to be destroyed before its pointee is handed to and processed by print
? Why doesn't this happen in the former expression?
I've managed to strip hana::compose
down to the minimum I need for my example:
#include <iostream>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
template <typename F, typename G>
struct compose {
F f; G g;
compose(F f, G g) : f{f}, g{g} {}
template <typename X>
decltype(auto) operator()(X const& x) const& {
return f(g(x));
}
};
struct Document {
std::string str;
Document(std::string&& str) : str{std::move(str)} {}
~Document() {
str = "bad";
};
};
void print(Document const& doc1) {
std::cout << doc1.str << std::endl;
}
auto deref = [](auto&& x) -> decltype(auto) {
return *std::forward<decltype(x)>(x);
};
auto makeDocument = [](std::string&& str) {
return std::make_shared<Document>(std::move(str));
};
const auto good = compose(compose(print, deref), makeDocument);
const auto bad = compose(print, compose(deref, makeDocument));
int main() {
good("good");
bad("good");
}
(The question was originally much longer. Look at the history to see where it comes from.)