Search code examples
c++templatesc++11variadic

Cannot remove unwanted overloads


The function transform conducted by

const std::vector<int>         a = {1,     2,       3,       4,    5};
const std::vector<double>      b = {1.2,   4.5,     0.6};
const std::vector<std::string> c = {"hi", "howdy", "hello", "bye"};
std::vector<double> result(5);

transform<Foo> (result.begin(),
    a.begin(), a.end(),
    b.begin(), b.end(),
    c.begin(), c.end());

is to carry out a generalization of std::transform on multiple containers, outputing the results in the vector result. One function with signature (int, double, const std::string&) would apparently be needed to handle the three containers in this example. But because the containers have different lengths, we instead need to use some overloads. I will test this using these member overloads of a holder class Foo:

static int execute (int i, double d, const std::string& s) {return i + d + s.length();}
static int execute (int i, const std::string& s) {return 2 * i + s.length();}
static int execute (int i) {return 3 * i - 1;}

However, the program will not compile unless I define three other overloads that are never even called, namely with arguments (int, double), (const std::string&) and (). I want to remove these overloads, but the program won't let me. You can imagine the problem this would cause if we had more than 3 containers (of different lengths), forcing overloads with many permutations of arguments to be defined when they are not even being used.

Here is my working program that will apparently show why these extraneous overloads are needed. I don't see how or why the are forced to be defined, and want to remove them. Why must they be there, and how to remove the need for them?

#include <iostream>
#include <utility>
#include <tuple>

bool allTrue (bool a) {return a;}

template <typename... B>
bool allTrue (bool a, B... b) {return a && allTrue(b...);}

template <typename F, size_t... Js, typename Tuple>
typename F::return_type screenArguments (std::index_sequence<>, std::index_sequence<Js...>, Tuple& tuple) {
    return F::execute (*std::get<Js>(tuple)++...);
}

// Thanks to Barry for coming up with screenArguments.
template <typename F, std::size_t I, size_t... Is, size_t... Js, typename Tuple>
typename F::return_type screenArguments (std::index_sequence<I, Is...>, std::index_sequence<Js...>, Tuple& tuple) {
    if (std::get<2*I>(tuple) != std::get<2*I+1>(tuple))
        return screenArguments<F> (std::index_sequence<Is...>{}, std::index_sequence<Js..., 2*I>{}, tuple);
    else
        return screenArguments<F> (std::index_sequence<Is...>{}, std::index_sequence<Js...>{}, tuple);
}

template <typename F, typename Tuple>
typename F::return_type passCertainArguments (Tuple& tuple) {
    return screenArguments<F> (std::make_index_sequence<std::tuple_size<Tuple>::value / 2>{},
        std::index_sequence<>{}, tuple);
}

template <typename F, typename OutputIterator, std::size_t... Is, typename... InputIterators>
OutputIterator transformHelper (OutputIterator result, const std::index_sequence<Is...>&, InputIterators... iterators) {
    auto tuple = std::make_tuple(iterators...);
    while (!allTrue(std::get<2*Is>(tuple) == std::get<2*Is + 1>(tuple)...))
        *result++ = passCertainArguments<F>(tuple);
    return result;
}

template <typename F, typename OutputIterator, typename... InputIterators>
OutputIterator transform (OutputIterator result, InputIterators... iterators) {
    return transformHelper<F> (result, std::make_index_sequence<sizeof...(InputIterators) / 2>{}, iterators...);
}

// Testing
#include <vector>

struct Foo {
    using return_type = int;
    static int execute (int i, double d, const std::string& s) {return i + d + s.length();}
    static int execute (int i, const std::string& s) {return 2 * i + s.length();}
    static int execute (int i) {return 3 * i - 1;}
    // These overloads are never called, but apparently must still be defined.  
    static int execute () {std::cout << "Oveload4 called.\n";  return 0;}
    static int execute (int i, double d) {std::cout << "Oveload5 called.\n";  return i + d;}
    static int execute (const std::string& s) {std::cout << "Oveload6 called.\n";  return s.length();}
};

int main() {
    const std::vector<int>         a = {1,     2,       3,       4,    5};
    const std::vector<double>      b = {1.2,   4.5,     0.6};
    const std::vector<std::string> c = {"hi", "howdy", "hello", "bye"};
    std::vector<double> result(5);

    transform<Foo> (result.begin(),
        a.begin(), a.end(),
        b.begin(), b.end(),
        c.begin(), c.end());
    for (double x : result) std::cout << x << ' ';  std::cout << '\n';
    // 4 11 8 11 14 (correct output)
}

Solution

  • The compiler does not know at compile time which combinations of functions will be used in runtime. So you have to implement all the 2^N functions for every combination. Also your approach will not work when you have containers with the same types.

    If you want to stick with templates, my idea is to implement the function something like this:

    template <bool Arg1, bool Arg2, bool Arg3>
    static int execute (int *i, double *d, const std::string *s);
    

    The template arguments Arg1, Arg2, Arg3 represent the validity of each parameter. The compiler will automatically generate all the 2^N implementations for every parameter combination. Feel free to use if statements inside this function instead of template specialization - they will be resolved at compile time to if (true) or if (false).