无法删除不需要的重载

时间:2015-09-28 23:24:49

标签: c++ templates c++11 variadic

执行的功能transform
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());

是对多个容器进行std::transform的泛化,在向量result中输出结果。在这个例子中,显然需要一个带有签名(int, double, const std::string&)的函数来处理三个容器。但由于容器长度不同,我们需要使用一些重载。我将使用持有者类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;}

但是,程序不会编译,除非我定义了三个从未调用的其他重载,即使用参数(int, double)(const std::string&)()。我想删除这些重载,但程序不会让我。你可以想象如果我们有超过3个容器(不同长度)会导致过多的问题,当它们甚至没有被使用时,可以定义多个参数的排列。

这是我的工作程序,显然会显示为什么需要这些无关的重载。我不知道如何或为什么强制定义,并想要删除它们。为什么他们必须在那里,以及如何消除他们的需要?

#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)
}

2 个答案:

答案 0 :(得分:1)

编译器在编译时不知道将在运行时使用哪些函数组合。因此,您必须为每个组合实现所有2^N函数。当您拥有相同类型的容器时,您的方法也不起作用。

如果你想坚持使用模板,我的想法就是实现这样的功能:

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

模板参数Arg1, Arg2, Arg3表示每个参数的有效性。编译器将自动为每个参数组合生成所有2^N实现。您可以在此函数中使用if语句而不是模板特化 - 它们将在编译时解析为if (true)if (false)

答案 1 :(得分:0)

我想我得到了!根据实际需要的参数数量模板化函数Foo::execute,并让它们都具有相同的参数:

struct Foo {
    using return_type = int;
    template <std::size_t> static return_type execute (int, double, const std::string&);
};

template <> Foo::return_type Foo::execute<3> (int i, double d, const std::string& s) {return i + d + s.length();}
template <> Foo::return_type Foo::execute<2> (int i, double, const std::string& s) {return 2 * i + s.length();}
template <> Foo::return_type Foo::execute<1> (int i, double, const std::string&) {return 3 * i - 1;}
template <> Foo::return_type Foo::execute<0> (int, double, const std::string&) {return 0;}  // The only redundant specialization that needs to be defined.

这是完整的解决方案。

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

bool allTrue (bool b) {return b;}

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

template <typename F, std::size_t N, typename Tuple, typename... Args>
typename F::return_type countArgumentsNeededAndExecute (Tuple&, const std::index_sequence<>&, Args&&... args) {
    return F::template execute<N>(std::forward<Args>(args)...);
}

template <typename F, std::size_t N, typename Tuple, std::size_t I, size_t... Is, typename... Args>
typename F::return_type countArgumentsNeededAndExecute (Tuple& tuple, const std::index_sequence<I, Is...>&, Args&&... args) {  // Pass tuple by reference, because its iterator elements will be modified (by being incremented).
    return (std::get<2*I>(tuple) != std::get<2*I + 1>(tuple)) ?
        countArgumentsNeededAndExecute<F, N+1> (tuple, std::index_sequence<Is...>{}, std::forward<Args>(args)...,  // The number of arguments to be used increases by 1.
            *std::get<2*I>(tuple)++) :  // Pass the value that will be used and increment the iterator.
        countArgumentsNeededAndExecute<F, N> (tuple, std::index_sequence<Is...>{}, std::forward<Args>(args)...,
            typename std::iterator_traits<typename std::tuple_element<2*I, Tuple>::type>::value_type{});  // Pass the default value (it will be ignored anyway), and don't increment the iterator.  Hence, the number of arguments to be used does not change.
}

template <typename F, typename OutputIterator, std::size_t... Is, typename... InputIterators>
OutputIterator transformHelper (OutputIterator result, const std::index_sequence<Is...>& indices, InputIterators... iterators) {
    auto tuple = std::make_tuple(iterators...);  // Cannot be const, as the iterators are being incremented.
    while (!allTrue(std::get<2*Is>(tuple) == std::get<2*Is + 1>(tuple)...))
        *result++ = countArgumentsNeededAndExecute<F, 0> (tuple, indices);  // Start the count at 0.  Examine 'indices', causing the count to increase one by one.
    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;
    template <std::size_t> static return_type execute (int, double, const std::string&);
};

// Template the function Foo::execute according to the number of arguments that are actually needed:
template <> Foo::return_type Foo::execute<3> (int i, double d, const std::string& s) {return i + d + s.length();}
template <> Foo::return_type Foo::execute<2> (int i, double, const std::string& s) {return 2 * i + s.length();}
template <> Foo::return_type Foo::execute<1> (int i, double, const std::string&) {return 3 * i - 1;}
template <> Foo::return_type Foo::execute<0> (int, double, const std::string&) {return 0;}  // The only redundant specialization that needs to be defined.

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)
}

使用Andrey Nasonov的bool模板的第二个解决方案需要生成所有2 ^ N个重载。请注意,上述解决方案仅需要N + 1模板即时过载。

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

bool allTrue (bool b) {return b;}

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

template <bool...> struct BoolPack {};

template <typename F, typename Tuple, bool... Bs, typename... Args>
typename F::return_type checkArgumentsAndExecute (const Tuple&, const std::index_sequence<>&, BoolPack<Bs...>, Args&&... args) {
    return F::template execute<Bs...>(std::forward<Args>(args)...);
}

template <typename F, typename Tuple, std::size_t I, size_t... Is, bool... Bs, typename... Args>
typename F::return_type checkArgumentsAndExecute (Tuple& tuple, const std::index_sequence<I, Is...>&, BoolPack<Bs...>, Args&&... args) {  // Pass tuple by reference, because its iterators elements will be modified (by being incremented).
    return (std::get<2*I>(tuple) != std::get<2*I+1>(tuple)) ?
        checkArgumentsAndExecute<F> (tuple, std::index_sequence<Is...>{}, BoolPack<Bs..., true>{}, std::forward<Args>(args)...,
            *std::get<2*I>(tuple)++) :  // Pass the value that will be used and increment the iterator.
        checkArgumentsAndExecute<F> (tuple, std::index_sequence<Is...>{}, BoolPack<Bs..., false>{}, std::forward<Args>(args)...,
            typename std::iterator_traits<typename std::tuple_element<2*I, Tuple>::type>::value_type{});  // Pass the default value (it will be ignored anyway), and don't increment the iterator.
}

template <typename F, typename OutputIterator, std::size_t... Is, typename... InputIterators>
OutputIterator transformHelper (OutputIterator& result, const std::index_sequence<Is...>& indices, InputIterators... iterators) {
    auto tuple = std::make_tuple(iterators...);  // Cannot be const, as the iterators are being incremented.
    while (!allTrue(std::get<2*Is>(tuple) == std::get<2*Is + 1>(tuple)...))
        *result++ = checkArgumentsAndExecute<F> (tuple, indices, BoolPack<>{});
    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;
    template <bool B1, bool B2, bool B3> static return_type execute (int, double, const std::string&) {return 0;}  // All necessary overloads defined at once here.
};

// Specializations of Foo::execute<B1,B2,B3>(int, double, const std::string&) that will actually be called by transform<Foo> (it is the client's responsibility to define these overloads based on the containers passed to transform<Foo>).
template <> Foo::return_type Foo::execute<true, true, true> (int i, double d, const std::string& s) {return i + d + s.length();}
template <> Foo::return_type Foo::execute<true, false, true> (int i, double, const std::string& s) {return 2 * i + s.length();}
template <> Foo::return_type Foo::execute<true, false, false> (int i, double, const std::string&) {return 3 * i - 1;}

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)
}