4

My goal is to get composition of functions working with this exact syntax:

int main() {
    Function<std::string, int> f([](const std::string& s) {return s.length();});
    Function<int, double> g([](int x) {return x + 0.5;});
    Function<double, int> h([](double d) {return int(d+1);});
    std::cout << compose(g, f, "hello") << '\n';  // g(f("hello")) = 5.5
    std::cout << compose(h, g, f, "hello") << '\n';  // h(g(f("hello"))) = 6
}

By changing the syntax slightly so that the "hello" argument goes first, I have it working easily with the following code:

#include <iostream>
#include <functional>
#include <tuple>
#include <string>

template <typename D, typename R>
struct Function {
    using domain = const D&;
    using range = R;
    using function = std::function<range(domain)>;
    const function& f;
    Function (const function& f) : f(f) {}
    range operator()(domain x) const {return f(x);}
};

template <typename... Ts>
struct LastType {
    using Tuple = std::tuple<Ts...>;
    using type = typename std::tuple_element<std::tuple_size<Tuple>::value - 1, Tuple>::type;
};

template <typename F, typename G>
typename G::range compose (const typename F::domain& x, const G& g, const F& f) {
    return g(f(x));
}

template <typename F, typename... Rest>
auto compose (const typename LastType<Rest...>::type::domain& x, const F& f, const Rest&... rest) {
    return f(compose(x, rest...));
}

int main() {
    Function<std::string, int> f([](const std::string& s) {return s.length();});
    Function<int, double> g([](int x) {return x + 0.5;});
    Function<double, int> h([](double d) {return int(d+1);});
    std::cout << compose("hello", g, f) << '\n';  // g(f("hello")) = 5.5
    std::cout << compose("hello", h, g, f) << '\n';  // h(g(f("hello"))) = 6
}

Having done that, I thought it would be a trivial task to adapt the above code so that I get the exact syntax I want (i.e. with "hello" being at the end of the list), but it is turning more difficult than I thought. I attempted the following, which does not compile:

#include <iostream>
#include <functional>
#include <tuple>
#include <string>

template <typename D, typename R>
struct Function {
    using domain = const D&;
    using range = R;
    using function = std::function<range(domain)>;
    const function& f;
    Function (const function& f) : f(f) {}
    range operator()(domain x) const {return f(x);}
};

template <typename F, typename G>
typename G::range compose (const G& g, const F& f, const typename F::domain& x) {
    return g(f(x));
}

template <typename F, typename... Rest>
auto compose (const F& f, const Rest&... rest) {
    return f(compose(rest...));
}

int main() {
    Function<std::string, int> f([](const std::string& s) {return s.length();});
    Function<int, double> g([](int x) {return x + 0.5;});
    Function<double, int> h([](double d) {return int(d+1);});
    std::cout << compose(g, f, "hello") << '\n';  // g(f("hello")) = 5.5
    std::cout << compose(h, g, f, "hello") << '\n';  // h(g(f("hello"))) = 6
}

And I don't know how to fix it. Can anybody help me fix this?

A new idea I've come up with is to define compose_, which will reorder the arguments of args... (by some std::tuple manipulation) so that the first element goes last and then passing that argument pack to compose. This looks very messy though, and even if it works, there must be a more direct (and shorter) solution.

max66
  • 65,235
  • 10
  • 71
  • 111
prestokeys
  • 4,817
  • 3
  • 20
  • 43

2 Answers2

3

It looks like the following also works:

template <typename T>
const T& compose (const T& t) {
    return t;
}

template <typename F, typename... Rest>
typename F::range compose(const F& f, Rest... rest) {
    return f(compose(rest...));
}
user1149913
  • 4,463
  • 1
  • 23
  • 28
1

what about in this way?

#include <iostream>    
#include <functional>
#include <tuple>
#include <string>

template <typename D, typename R>
struct Function {
    using domain = const D&;
    using range = R;
    using function = std::function<range(domain)>;
    const function& f;
    Function (const function& f) : f(f) {}
    range operator()(domain x) const {return f(x);}
};

template <typename F, typename X = typename F::domain>
typename F::range compose (const F& f, const X & x) {
    return f(x);
}

template <typename F, typename... Rest>
typename F::range  compose (const F& f, const Rest&... rest) {
    return f(compose(rest...));
}

int main() {
    Function<std::string, int> f([](const std::string& s) {return    s.length();});
    Function<int, double> g([](int x) {return x + 0.5;});
    Function<double, int> h([](double d) {return int(d+1);});
    std::cout << compose(g, f, "hello") << '\n';  // g(f("hello")) = 5.5
    std::cout << compose(h, g, f, "hello") << '\n';  // h(g(f("hello"))) = 6
}

You can use auto for the returning type of compose() only in c++14 (if I'm not wrong).

Your version doesn't compile because your variadic version of compose() uses N variadic types and N arguments when the final (not varidic) uses 2 types and 3 arguments. In other words, the variadic version lost the final argument. Your version doesn't compile because the final (not variadic version) is never used: the compiler chooses the variadic version. Adding typename X = typename F::domain (and changing const typename F::domain& with const X&) the final version is preferred and your code should compile (with c++14, at least) [corrected by Piotr Skotnicki; thanks]

p.s.: sorry for my bad English.

ABu
  • 10,423
  • 6
  • 52
  • 103
max66
  • 65,235
  • 10
  • 71
  • 111
  • 1
    "*Your version don't compile because the variadic version of compose() is with N variadic types and N arguments when the final (not variac) is with 2 variadic types and 3 arguments.*", I think OP's code does not compile because whatever `Rest` deduces, it is an exact match compared to `const typename F::domain&` – Piotr Skotnicki Mar 12 '16 at 17:11
  • @ Piotr: your right about one point: my explanation is wrong. – max66 Mar 12 '16 at 17:39
  • @Piotr: and, yes, your right: changing the template definition of the variadic version of OP's code from `template ` to `template `, the OP's code compile – max66 Mar 12 '16 at 17:51
  • `typename X = typename F::domain` doesn't make much sense, since `X` will be deduced anyway – Piotr Skotnicki Mar 12 '16 at 20:24
  • @Piotr: I'm not sure (I'm not really an expert) but I think the problem (in Prestokey code) is that the final (not variadic) version of `compose()` was expected to receive, as last parameter, a `type::domain` that in (in the examples) a` std::string const`. (continue) – max66 Mar 13 '16 at 17:21
  • @Piotr: (continue) In the `main()` function, the last parameter for `compose()` is a `char const *`, which can be converted to an `std::string const` but isn't, exactly, a `std::string const`. (continue) – max66 Mar 13 '16 at 17:23
  • @Piotr: (continue) So, for `compose(g, f, x)`, the compiler choose the variadic version of `compose()`, not the final version; in this way, the final version is never used, and the compiler is unable to close the chain. On the contrary, by introducing an additional type template (the default type is unnecessary), the final version (not variadic) corresponds to an exact match (where the last parameter is a generic type), is more specialized than the variadic version and, therefore, is selected. In other words, another way to fix the program was pass `std::string("hello")` instead of `"hello"`. – max66 Mar 13 '16 at 17:23
  • what is `typename X = typename F::domain` supposed to do in your code? `X` will be deduced, it's not set to `F::domain` – Piotr Skotnicki Mar 13 '16 at 17:33
  • @Piotr: are you right: is unusefull. But my entire solution it's over-complicated; much more simple and elegant the User1149913 one. – max66 Mar 13 '16 at 18:52