C ++模板:调用两个选项之间匹配的函数

时间:2016-03-12 15:19:27

标签: c++ variadic-templates

如何使用C ++模板调用匹配的函数?例如,如果我有函数a和b:

void a_impl(string, int){}
void b_impl(int, string){}

template<typename X, typename Y>
void a(X x, Y y){
  a_impl(x, y);
}

template<typename X, typename Y>
void b(X x, Y y){
  b_impl(x, y);
}

template<typename X, typename Y>
void a_or_b(X x, Y y);

如何实施a_or_b,如果匹配则调用a(x, y),否则请致电b(x, y)

我尝试制作的是for_each函数,可以处理这些情况:

vector<pair<string, int>> v1 = {{"one", 1}, {"two", 2}};

for_each(v1, [](string x, int y){
  cout << x << " " << y << endl;
});

vector<int> v2 = {1, 2, 3};

for_each(v2, [](int x){
  cout << x << endl;
});

到目前为止,我已独立地为元组和单个变量工作,但我希望自动选择适当的版本。这是我到目前为止的实施情况; unpack apply_from_tuple来自此页面http://www.cppsamples.com/common-tasks/apply-tuple-to-function.html。{/ p>

template<typename Range, typename Func>
void for_each_unpack(Range && range, Func && func){
  for (auto && element : range){
    using Element = decltype(element);
    unpack(std::forward<Func>(func), std::forward<Element>(element));
  }
}

template<typename Range, typename Func>
void for_each_nounpack(Range && range, Func && func){
  for (auto && element : range){
    using Element = decltype(element);
    std::forward<Func>(func)(std::forward<Element>(element));
  }
}

编辑:感谢@jotik让它工作。我把代码放在github上https://github.com/csiz/for_each

2 个答案:

答案 0 :(得分:3)

使用包含decltypeSFINAE的结尾回复类型:

#include <iostream>
#include <string>
#include <utility>


void a(std::string, int) { std::cout << "a" << std::endl; }
void b(int, std::string) { std::cout << "b" << std::endl; }

template <typename ... Args>
auto a_or_b(Args && ... args)
    -> decltype(a(std::forward<Args>(args)...))
{ return a(std::forward<Args>(args)...); }

template <typename ... Args>
auto a_or_b(Args && ... args)
    -> decltype(b(std::forward<Args>(args)...))
{ return b(std::forward<Args>(args)...); }

int main() {
    std::string s;
    int i;
    a_or_b(s, i); // calls a
    a_or_b(i, s); // calls b
}

我在上面的示例中使用了完美转发,因为它避免了每个参数的副本,但是使用显式类型的不太通用的天真解决方案也有效:

template <typename X, typename Y>
auto a_or_b(X x, Y y) -> decltype(a(x, y))
{ return a(x, y); }

template <typename X, typename Y>
auto a_or_b(X x, Y y) -> decltype(b(x, y))
{ return b(x, y); }

SFINAE在这种情况下的工作原理如下。请注意,a_or_b有2个模板定义。当您向a_or_b编写函数调用时,编译器会尝试确定您要调用的a_or_b。由于SFINAE,它会忽略任何无法推断出其类型的模板a_or_b。例如。对于调用a_or_b(s, i);,第二个decltype(b(std::forward<Args>(args)...))定义的(尾随)返回类型a_or_b不起作用,因此编译器不会考虑第二个a_or_b定义。

在这种情况下,返回类型必须是尾随返回类型,因为它取决于函数参数。例如,以下内容无法编译:

template <typename ... Args>
decltype(b(std::forward<Args>(args)...)) a_or_b(Args && ... args)
{ return b(std::forward<Args>(args)...); }

答案 1 :(得分:1)

这是你想法的有趣变化。传递一组函数和一组参数。然后调用该元组中的所有函数(从左到右),它们可以接受那些给定的参数。此外,存储元组中调用的所有返回值(如果有)。在这里,我使用SFINAE中的void_t(C ++ 17中的std::void_t)方面:

#include <iostream>
#include <tuple>
#include <type_traits>

template <typename T>
using void_t = void;

template <std::size_t N, typename VoidTFailed, typename EnableIfFailed, typename Tuple, typename... Args>
struct Try {
    static bool execute (Tuple&&, Args&&...) {
        std::cout << "No function found.\n";
        return false;
    }
};

template <std::size_t N, typename VoidTFailed, typename Tuple, typename... Args>
struct Try<N, VoidTFailed, std::enable_if_t<(N < std::tuple_size<Tuple>::value)>, Tuple, Args...> : std::conditional_t<(N < std::tuple_size<Tuple>::value - 1),
    Try<N+1, void, void, Tuple, Args...>,
    Try<N+1, int, int, Tuple, Args...>  // If N == std::tuple_size<Tuple>::value - 1, then simply inherit from the primary template above, as inheriting from Try<N+1, void, void, Tuple, Args...> will cause a compiling error when std::tuple_element_t<N+1, Tuple> is instantiated in the specialization below.
> {};

struct NoReturnValue {
    friend std::ostream& operator<< (std::ostream& os, const NoReturnValue&) {
        return os << "[no value returned]";
    }
};

template <std::size_t N, typename Tuple, typename EnableIfFailed, typename... Args>
struct Continue {
    static auto execute (Tuple&& functionTuple, Args&&... args) {
        const auto singleTuple = std::make_tuple (std::get<N>(std::forward<Tuple>(functionTuple))(std::forward<Args>(args)...));
        return std::tuple_cat (singleTuple, Try<N+1, void, void, Tuple, Args...>::execute(std::forward<Tuple>(functionTuple), std::forward<Args>(args)...));
    }
};

template <std::size_t N, typename Tuple, typename... Args>  // This specialization is only used if the function in question returns void.
struct Continue<N, Tuple, std::enable_if_t<std::is_void<decltype(std::declval<std::tuple_element_t<N, std::decay_t<Tuple>>>()(std::declval<std::decay_t<Args>>()...))>::value>, Args...> {
    static auto execute (Tuple&& functionTuple, Args&&... args) {
        std::get<N>(std::forward<Tuple>(functionTuple))(std::forward<Args>(args)...);
        return std::tuple_cat (std::make_tuple(NoReturnValue{}), Try<N+1, void, void, Tuple, Args...>::execute(std::forward<Tuple>(functionTuple), std::forward<Args>(args)...));
    }
};

template <std::size_t N, typename Tuple, typename EnableIfFailed, typename... Args>
struct Stop {
    static auto execute (Tuple&& functionTuple, Args&&... args) {
        return std::make_tuple (std::get<N>(std::forward<Tuple>(functionTuple))(std::forward<Args>(args)...));
    }
};

template <std::size_t N, typename Tuple, typename... Args>  // This specialization is only used if the function in question returns void.
struct Stop<N, Tuple, std::enable_if_t<std::is_void<decltype(std::declval<std::tuple_element_t<N, std::decay_t<Tuple>>>()(std::declval<std::decay_t<Args>>()...))>::value>, Args...> {
    static auto execute (Tuple&& functionTuple, Args&&... args) {
        std::get<N>(std::forward<Tuple>(functionTuple))(std::forward<Args>(args)...);
        return std::make_tuple(NoReturnValue{});
    }
};

template <std::size_t N, typename Tuple, typename... Args>
struct Try<N, void_t<decltype(std::declval<std::tuple_element_t<N, Tuple>>()(std::declval<Args>()...))>,
        std::enable_if_t<(N < std::tuple_size<Tuple>::value)>, Tuple, Args...> : std::conditional_t<(N < std::tuple_size<Tuple>::value - 1),
    Continue<N, Tuple, void, Args...>,
    Stop<N, Tuple, void, Args...>
> {};

template <typename Tuple, typename... Args>
auto executeAllPossible (Tuple&& functionTuple, Args&&... args) {
    return Try<0, void, void, Tuple, Args...>::execute(std::forward<Tuple>(functionTuple), std::forward<Args>(args)...);
}

// Testing
int foo (int, char, bool, int, int) {std::cout << "foo\n";  return 8;}
bool bar (int, char, bool, double, long, bool) {std::cout << "bar\n";  return true;}
void goo (int, char&&, bool, float, int) {std::cout << "goo\n";}
double baz (int, const char, bool&&, double, std::size_t) {std::cout << "baz\n";  return 4.5;}

template <typename Ch, typename Tr, typename Tuple, std::size_t... Is>
void print_tuple_impl (std::basic_ostream<Ch, Tr>& os, const Tuple& t, std::index_sequence<Is...>) {
    using A = int[];
    (void)A{(void(os << (Is == 0 ? "" : ", ") << std::get<Is>(t)), 0)...};
}

template <typename Ch, typename Tr, typename... Args>
decltype(auto) operator<< (std::basic_ostream<Ch, Tr>& os, const std::tuple<Args...>& t) {
    os << "tuple{";
    print_tuple_impl(os, t, std::index_sequence_for<Args...>{});
    return os << "}";
}

int main() {
    const auto tuple = executeAllPossible (std::make_tuple(foo, bar, goo, baz), 2, 'c', true, 3.5, 8);
    std::cout << std::boolalpha << "output = " << tuple << '\n';
}

输出:

foo  goo  baz
output = tuple{8, [no value returned], 4.5}