3

Suppose I have written generic map functions for STL tuples(tuple, pair), as well as STL sequences (vector, list, deque). Now I want to write a global map function calling the appropriate special functions given the input types.

I have something along the lines of

template <typename... Ts>
struct mappable {
    static constexpr bool is_instance = false;
};

template <typename... Tuples, typename = require<all<is_stl_tuple, Tuples...>::value>>
struct mappable<Tuples...> {
  template <typename Func>
  auto map(Func&& f, Tuples&&... ts) {
     return tuple::map(std::forward<Func>(f), ts...);
  }

  static constexpr bool is_instance = true;
};

template <typename... Sequences, typename = require<all<is_stl_sequence, Sequences...>::value>>
struct mappable<Sequences...> {
    template <typename Func>
    auto map(Func&& f, Sequences&&... seqs) {
        return sequence::map(std::forward<Func>(f), seqs...);
    }

    static constexpr bool is_instance = true;
};

template <typename Func, typename... Ts>
auto map(Func&& f, Ts&&... ts) {
    static_assert(mappable<Ts...>::is_instance, "Tried calling map on unsupported types. Mappable arguments must be supplied.");
    return mappable<Ts...>::map(std::forward<Func>(f), std::forward<Ts>(ts)...);
}

Although hopefully self-explanatory the type-checking function defs:

// true iff Unary<Ts>::value... == true for at least one Ts
template <template <typename> class Unary, typename... Ts>
struct any;

// true iff Unary<Ts>::value... == true for all Ts
template <template <typename> class Unary, typename... Ts>
struct all;

template <bool B>
using require = typename std::enable_if<B>::type;

Obviously this won't (and does not) work since I specialize mappable on the default arguments. Is there any way to do this and if not (and I have to redesign), how would you go about redesigning those functions? sequence::map shall for example take any combination of stl sequences, so all ideas I have about restructuring just shift the problem elsewhere...

Thanks in advance for any help...

Edit: As requested here are usage examples (actually my test code for it) before I started doing the above:

auto t0 = std::make_tuple(2.f, -5, 1);
auto t1 = std::make_tuple(1, 2);
auto b0 = tuple::map([] (auto v) { return v > decltype(v)(0); }, t0);
auto r0 = tuple::map([] (auto v0, auto v1) { return v0 + v1; }, t0, t1);
// b0 is tuple<bool, bool, bool>(true, false, true)
// b1 is tuple<float, int>(3.f, -3)

and for sequences:

std::vector<float> s0 = {1.f, 2.f, 3.f, 0.f};
std::list<int>   s1 = {3, 0, -2};
auto r = sq::map([] (auto v0, auto v1) { return v0 + v1; }, s0, s1);
// type of r is compound type of first argument (vector here), result is
// vector<float>(4.f, 2.f, 1.f)

Implementations of these map functions are completely different - the aim of my approach above is to be able to drop the namespace and just use map having it doing The Right Thing.

Casey
  • 41,449
  • 7
  • 95
  • 125
Richard Vock
  • 1,286
  • 10
  • 23
  • By the way, you're abusing `forward`. It's for *deduced* types only. – Kerrek SB Jan 19 '14 at 15:19
  • And can you please give a usage example? – Kerrek SB Jan 19 '14 at 15:20
  • @KerrekSB: 1. You are referring to std::forward... and std::forward... I hope and would be completey right about that I guess. 2. Edited the question to give some exampes... – Richard Vock Jan 19 '14 at 15:28
  • I'd do this: Write one overload for tuples, one for pairs, and one for containers. They should all be templates, and the last one should use some sort of `is_container` trait, like [the pretty printer](http://stackoverflow.com/questions/4850473/pretty-print-c-stl-containers) does it. You could refine this approach to support finer degrees of lvalue-vs-rvalueness if you wanted. – Kerrek SB Jan 19 '14 at 16:13
  • @KerrekSB Actually I thought my question was about how to "write one overload for tuples [...] and one for containers". All overloads would have the same parameters (Func and XYZ...). Yuris answer suggests that I should (which I find kind of curious) overload based on return types - although that leads to some other problems... Edit: I should have made more clear, that while I tried SFINAE on a type class above, the question was meant to be more general about this overloading problem. – Richard Vock Jan 19 '14 at 16:37
  • You've missed `std::array`, which I believe is both a sort-of tuple *and* a sort-of sequence. – Casey Jan 19 '14 at 18:48
  • you are completely right - I'll have to consider that, too – Richard Vock Jan 19 '14 at 19:02

1 Answers1

5

Why not just two function overloads?

template <typename Func, typename... Tuples,
    require<all<is_stl_tuple, Tuples...>>...>
auto map(Func&& f, Tuples&&... ts) {
    return tuple::map(std::forward<Func>(f), std::forward<Tuples>(ts)...);
}

template <typename Func, typename... Sequences,
    require<all<is_stl_sequence, Sequences...>>...>
auto map(Func&& f, Sequences&&... seqs) {
    return sequence::map(std::forward<Func>(f), std::forward<Sequences>(seqs)...);
}

which makes use of a tweak to require, for the two overloads to play nice:

template <bool condition>
struct require_impl {};

template <>
struct require_impl<true> {
    enum class type {};    
};

template <typename Condition>
using require = typename require_impl<Condition::value>::type;
Luc Danton
  • 34,649
  • 6
  • 70
  • 114
yuri kilochek
  • 12,709
  • 2
  • 32
  • 59
  • I got an equivalent problem with that, being that the compiler calls the second one a redefinition of the first - are you able to compile this code? – Richard Vock Jan 19 '14 at 15:44
  • @RichardVock now I am, had to reimplement some of your stuff though. – yuri kilochek Jan 19 '14 at 16:02
  • First of all thank you for the work (should have supplied the entire implementation). What I noted about this solution is that your return type would be void. enable_if has a second template param to substitute that, so I could in theory use decltype(xyz::map(...) for that, however the return type of tuple::map(...) relies on std::tuple_size, so I get a compilation error. Nonetheless your answers steers me in the right direction and the problem I still have is more like a separate question ;) Upvote and accept follows in a few minutes... – Richard Vock Jan 19 '14 at 16:32
  • *"Putting it in return type works on gcc 4.8.1:"* Because the return type is part of the signature of a function template, whereas the default arguments are not. See [defns.signature.templ] – dyp Jan 19 '14 at 16:33
  • @RichardVock it is possible to move fsinae test from return type, see edit 2. – yuri kilochek Jan 19 '14 at 16:43
  • Whew - that's some nice compiler magic there. I like it and it works perfectly. – Richard Vock Jan 19 '14 at 16:53
  • @LucDanton if you look closely I actually used the technique described in that article in `require` – yuri kilochek Jan 20 '14 at 12:31
  • @LucDanton sure, knock yourself out. The current arrangement is a result of back-and-forth with OP. – yuri kilochek Jan 20 '14 at 12:51