如何创建可以采用函数指针和 lambda 的模板函数

How to create template function that can take function pointer and lambda

我想创建一个方法,它接受一些参数 T,布尔函数并用它做一些事情(假设如果 func(param) 为真,则打印它)。 我遇到的问题是:

当您在没有模板的情况下编写代码时,以下两个示例都有效:

static bool is_even(int i) {
    return i % 2 == 0;
}

void f(const int &d, bool (*f) (int)) {
    cout << (f(d) ? "true" : "false");
}

//void f(const int &d, std::function<bool(int)> f) {
//    cout << (f(d) ? "true" : "false");
//}

f(10, [](int e) -> bool { return e % 2 == 0; });
f(10, is_even);

即使我注释掉第二个函数并先注释,它仍然有效。 但是当我像这样添加模板时:

template<typename T>
void f(const T &d, bool (*f) (T)) {
    cout << (f(d) ? "true" : "false");
}

或者这个

template<typename T>
void f(const T &d, std::function<bool(T)> f) {
    cout << (f(d) ? "true" : "false");
}

它说

no instance of function template "f" matches the argument list argument types are: (int, lambda []bool (int e)->bool)

如果我用std::function,它也会说

no instance of function template "f" matches the argument list argument types are: (int, bool (int i))  

所以问题是:我怎样才能让这个模板既适用于函数又适用于 lambda?

编辑: 我想我最初提供的信息少于需要的信息。 问题是我想用这样的不同函数多次重载运算符:

template<typename T>
vector<T> operator|(const vector<T> &vec, void (*f) (T)) {
    // ...
}

template<typename T>
vector<T> operator|(const vector<T> &vec, bool (*f) (T)) {
    // ...
}

template<typename TIn, typename TOut>
vector<TOut> operator|(const vector<TIn> &vec, TOut (*f) (TIn)) {
    // ...
}

Lambda 和 std::function 与函数指针的类型不同,尽管它们可能可以相互转换。但是,在模板类型推导中,类型不会被隐式转换,这就是为什么你没有有效匹配。

您可以通过显式指定模板类型来强制类型

f<int>(10, [](int e) -> bool { return e % 2 == 0; });

在这种情况下,代码几乎可以编译,但编译器会抱怨您对 f 的定义不明确,因为指针重载是一个同样好的匹配。所以,只保留 std::function 重载,并手动指定模板类型

#include <iostream>
#include <functional>

static bool is_even(int i) {
    return i % 2 == 0;
}

template<typename T>
void f(const T &d, std::function<bool(T)> f) {
    std::cout << (f(d) ? "true" : "false") << std::endl;
}

int main()
{
    f<int>(10, [](int e) -> bool { return e % 2 == 0; });
    f<int>(10, is_even);
}

你可以这样做:

template<typename T, typename F>
void func(const T &d, F f) {
    std::cout << (f(d) ? "true" : "false");
}

这里有一个例子:

#include <cmath>
#include <functional>
#include <iomanip>
#include <iostream>
#include <type_traits>
using namespace std;

template <typename T, typename F>
auto func(T && t, F && f)
    -> typename enable_if<is_same<bool, typename result_of<F(T)>::type>::value, bool>::type
{
    return f(t);
}

bool is_pi(double d)
{
    // approximately
    return d == 3.1415926;
}

int main()
{
    cout << boolalpha << func(42, [](int answer){ return answer == 42; }) << endl;
    cout << boolalpha << func(M_PI, is_pi);
    return 0;
}

首先,各种函数指针、函数对象或 lambda 都是允许的。然后 enable_if 决定是否可以为给定类型实例化模板,具体取决于它是否支持 T 类型的参数和 returns bool.

看这里:ideone

问题是在模板参数推导期间不检查隐式转换。

看来你想要的是在第二个参数中关闭模板参数推导。您可以通过强制 T 处于非推导上下文中来做到这一点。我们将使用标准中指定的以下非推导上下文。

5 The non-deduced contexts are:

(5.1) — The nested-name-specifier of a type that was specified using a qualified-id.

template <typename T> struct identity { using type = T; };

template <typename T> using identity_t = typename identity<T>::type;

template<typename T>
void f(const T &d, bool (*g) (identity_t<T>)) {
  cout << (g(d) ? "true" : "false");
}

/* or */

template<typename T>
void f(const T &d, std::function<bool(identity_t<T>)> f) {
  cout << (f(d) ? "true" : "false");
}

它们都适用于:

f(10, [](int e) -> bool { return e % 2 == 0; });
f(10, is_even);

由于您使用的是 lambda,我建议您使用 template<typename T> void f(const T&, std::function<bool(T)>) 定义,这样您就可以在需要时支持捕获 lambda。

你的实现很好,你只需要通过指定来帮助 C++,而不是期望它推导 T。此代码在 Visual Studio 2013 和 gcc 4.9.2:

中运行良好
#include <iostream>
#include <functional>

using namespace std;

static bool is_even(int i) {
    return i % 2 == 0;
}

template<typename T>
void f(const T &d, function<bool(T)> f) {
    cout << (f(d) ? "true" : "false");
}

int main() {
    f<int>(10, [](int e){ return e % 2 == 0; });
    f<int>(10, is_even);

    return 0;
}

请注意,我指定的是模板类型 int。问题是 C++ 不知道 T 应该基于你的第一个还是第二个参数。如果是相同的类型就好了,但是编译器无法推导出std::function的参数类型,所以无法确认T的类型是否一致。通过传入类型,编译器不再需要推导它。

我知道您将问题简化到所提出的解决方案不再适用于原始问题的程度,因此这里有一个适用于您的原始问题的解决方案:

你想要做的是在编译时找到你传递给函数的任何东西的 return 类型(无论是 lambda 还是函数指针),然后根据它做不同的事情类型。您可以通过引入一个找到 return 类型然后调用实际函数的中间函数来实现。

template<typename T>
void real_foo(const vector<T> &vec, void (*f)(T)) {
    cout << "void";
}

template<typename T>
void real_foo(const vector<T> &vec, bool (*f)(T)) {
    cout << "bool";
}

template<typename TIn, typename TOut>
void real_foo(const vector<TIn> &vec, TOut (*f)(TIn)) {
    cout << "generic";
}

template<typename TIn, typename F>
void operator|(const vector<TIn> &vec, F &&f) {
    using TOut = decltype(f(declval<TIn>()));
    TOut (*fp)(TIn) = f;
    real_foo(vec, fp);
}

如果你想传递不可转换为函数指针的 lambda(例如,如果它们捕获某些东西),你需要更详细的东西(请注意,如果你不需要使用 std::function你只是在执行 lambda 而不是存储它)。

template <typename TIn, typename F, typename TOut>
struct Foo {
    static void foo(const vector<TIn> &vec, F &&f) {
        cout << "generic";
    }
};

template <typename TIn, typename F>
struct Foo<TIn, F, void> {
    static void foo(const vector<TIn> &vec, F &&f) {
        cout << "void";
    }
};

template <typename TIn, typename F>
struct Foo<TIn, F, bool> {
    static void foo(const vector<TIn> &vec, F &&f) {
        cout << "bool";
    }
};

template<typename TIn, typename F>
void operator|(const vector<TIn> &vec, F &&f) {
    using TOut = decltype(f(declval<TIn>()));
    Foo<TIn, F, TOut>::foo(vec, forward<F>(f));
}

现在您可以按照您设想的方式使用运算符了:

vec | [](int){};
vec | [](int){ return true; };
vec | [](int){ return 5; };
vec | is_even;