There are multiple kinds of polymorphism in C++. By polymorphism, I mean whenever a single variable in code has different implementation types.
There is classic C++ inheritance and virtual based polymorphism. There is type erasure based polymorphism. And there is static polymorphism of templates.
In many senses, these kinds of polymorphism are opposed to each other. If you use one when you should be using the other, it is like using covariance when you should be using contravariance. Your code might stumble along, but it will only work when forced, like taking a square peg and a round hole and a big hammer.
Your <int> requirement is an example of using the wrong kind of polymorphism, and the <int> is the hammer smashing it into the wrong-shaped hole.
You are attempting to use
template <typename X>
using Callback = std::function<void(X)>;
and
template <typename X, typename Y>
using Fun = std::function<Y(X)>;
as pattern matchers. They aren't pattern matchers, even if in specific cases they can be used as pattern matchers. Callback and Fun are a type erasers.
Callback<X> takes anything that can be called with something that can be converted from an X, and stores it. Then forgets almost every other fact about it (well, it remembers how to copy it, its typeid, and a few other random facts).
Fun<X,Y> takes anything that can be called with something that can be converted from an X, and whose return value can then be converted to a Y. It then forgets almost every other fact about it.
Here:
template <typename Y>
auto map(Fun<X, Y>&& f) { return F_map(self(), f); }
you are trying to use it to say "I accept an f. Please find me a Y that would match this f".
This is pattern matching. Type erasure and pattern matching are opposite operations.
This is a really common mistake. With classic inheritance, they sometimes end up being the same thing.
std::function is for forgetting information about something, being able to store it, then later using only the parts you remember.
The first question is, do you need to pattern match, or do you need a type function here?
Probably you are good with a type function.
template <class F, class R = std::invoke_result_t<F, X>>
F_map<X,R> map(F&& f) { return {self(), std::forward<F>(f)}; }
here we map the incoming F to its return value R.
Your code has other issues. Like dangling pointers. Also, it insists on knowing what types the callables use; in C++, you can ... just not bother knowing that.
So, using the CRTP for static polymorphism, and mechanically forgetting what types I work on an replacing them with non-type erased code, I get:
#include <cstdio>
#include <type_traits>
template <class Upstream, class F>
struct F_map;
template<class D>
struct mappable
{
template <class F>
F_map<D, F> map(F const& f) { return F_map(static_cast<D*>(this), f); }
};
template <class Upstream, class F>
struct F_map:
mappable<F_map<Upstream, F>>
{
Upstream* upstream;
F f;
F_map(Upstream* upstream, const F& f) : upstream(upstream), f(f) {}
template<class Callback>
void operator()(Callback&& callback) {
(*upstream)([=](auto&& x) {
callback(f(decltype(x)(x)));
});
}
};
template <typename X>
struct F_id:
mappable<F_id<X>>
{
const X x;
F_id(const X& x) : x(x) {}
template<class Callback>
void operator()(Callback&& callback) { callback(x); }
};
int main(int argc, char* argv[]) {
auto f =
F_id(10)
.map([](int x) { return x + 2; })
.map([](int x) { return "1, 2, 3"; });
f([](const char* x) { printf("%s\n", x); });
return 0;
}
Live example.
I still think you are following dangling pointers, but I am not sure.
The return value of map stores a pointer to the object we call it on, and that object was a temporary destroyed when we made f.
To fix the Upstream* problem, I'd do this:
template <class Upstream, class F>
struct F_map;
template<class D>
struct mappable
{
template <class F>
F_map<D, F> map(F const& f) const { return {*static_cast<D const*>(this), f}; }
};
template <class Upstream, class F>
struct F_map:
mappable<F_map<Upstream, F>>
{
Upstream upstream;
F f;
F_map(Upstream const& upstream, const F& f) : upstream(upstream), f(f) {}
template<class Callback>
void operator()(Callback&& callback) const {
upstream([=](auto&& x) {
callback(f(decltype(x)(x)));
});
}
};
template <typename X>
struct F_id:
mappable<F_id<X>>
{
const X x;
F_id(const X& x) : x(x) {}
template<class Callback>
void operator()(Callback&& callback) const { callback(x); }
};
copy upstream by value.