简体   繁体   English

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

[英]How to create template function that can take function pointer and lambda

I want to create a method that takes some parameter T, boolean function and does something with it (let's say print it if func(param) is true). 我想创建一个方法,它接受一些参数T,布尔函数并对它做一些事情(假设如果func(param)为真则打印它)。 The problem I have encountered is that: 我遇到的问题是:

when you write code without templates, both following examples work: 当您编写没有模板的代码时,以下两个示例都有效:

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

even if I comment out second function and comment first, it would still work. 即使我先评论第二个功能并发表评论,它仍然有效。 But when I add template like this: 但是当我添加这样的模板时:

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

Or this 或这个

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

It says 它说

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

If I use std::function, it also says 如果我使用std :: function,它也会说

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

So the question is: How can I make this template work both with functions and with lambdas? 所以问题是:如何使这个模板兼具函数和lambdas?

edit: I think I initially gave less information than needed. 编辑:我想我最初提供的信息少于所需信息。 The thing is that I want to overload an operator several times with different functions like this: 事实是,我想使用不同的函数多次使运算符重载:

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)) {
    // ...
}

The issue is that implicit conversions are not checked during template argument deduction. 问题是在模板参数推断期间不会检查隐式转换。

It seems like what you want is to turn off template argument deduction in the second parameter. 看起来你想要的是在第二个参数中关闭模板参数推导。 You can do that by forcing T be in a non-deduced context. 您可以通过强制T在非推导的上下文中来实现。 We'll use the following non-deduced context specified in the standard. 我们将使用标准中指定的以下非推导上下文。

5 The non-deduced contexts are: 5未推断的背景是:

(5.1) — The nested-name-specifier of a type that was specified using a qualified-id . (5.1) - 使用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");
}

Both of them works for: 它们都适用于:

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

You can do: 你可以做:

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

Lambdas and std::function are not of the same type as a function pointer, although they may be inter-convertible. Lambdas和std::function与函数指针的类型不同,尽管它们可以是可互换的。 However, in template type deduction, types are not implicitly converted, and that's why you have no valid matching. 但是,在模板类型推导中,不会隐式转换类型,这就是您没有有效匹配的原因。

You can force the type by explicitly specifying the template type 您可以通过显式指定模板类型来强制类型

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

In this case, the code will almost compile, but the compiler complains that you have an ambiguous definition of f , since the pointer overload is an equally good match. 在这种情况下,代码几乎会编译,但编译器抱怨你有一个模糊的f定义,因为指针重载是一个同样好的匹配。 So, keep only the std::function overload, and manually specify the template type 因此,只保留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);
}

Here is an example how you could do it: 这是一个如何做到的例子:

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

In the first place every kind of function pointer, function object or lambda is allowed. 首先,允许各种函数指针,函数对象或lambda。 Then the enable_if decides if the template can be instantiated for the given type depending on if it supports a parameter of type T and returns a bool. 然后,enable_if决定是否可以为给定类型实例化模板,具体取决于它是否支持类型为T的参数并返回bool。

See here: ideone 见这里: ideone

Since your using a lambda, I'd recommend going with your template<typename T> void f(const T&, std::function<bool(T)>) definition, that'll allow you to support capturing lambdas down the road if you so desire. 由于你使用lambda,我建议你使用你的template<typename T> void f(const T&, std::function<bool(T)>)定义,这将允许你支持捕获lambdas如果你这么渴望。

Your implementation of that is fine you just need to help C++ out by specifying instead of expecting it to deduce T . 你的实现很好,你只需要通过指定而不是期望它推断T来帮助C ++。 This code works fine in Visual Studio 2013 and gcc 4.9.2 : 此代码在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;
}

Note that I'm specifying the template type int . 请注意,我正在指定模板类型int The problem is that C++ doesn't know whether T should be based on your first or second parameter. 问题是C ++不知道T是否应该基于你的第一个或第二个参数。 It's fine if they are the same type, but the compiler cannot deduce the parameter type of std::function , so it cannot confirm the type T is consistent. 如果它们是相同的类型就没关系,但是编译器不能推断出std::function的参数类型,所以它不能确认类型T是否一致。 By passing in the type, the compiler no longer has to deduce it. 通过传入类型,编译器不再需要推导它。

I know the situation where you simplify your problem to an extent where the proposed solution does not work for the original problem anymore, so here is a solution that works for your original problem: 我知道你将问题简化到建议的解决方案不再适用于原始问题的情况,所以这里有一个适用于你原始问题的解决方案:

What you want to do is you want to find the return type of whatever you pass to your function (be it a lambda or a function pointer) at compile time and then do different things based on that type. 你想要做的是你想要在编译时找到传递给函数的任何返回类型(无论是lambda还是函数指针),然后根据该类型执行不同的操作。 You can achieve that by introducing an intermediate function that finds the return type and then calls the real function. 您可以通过引入查找返回类型的中间函数然后调用实函数来实现。

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

If you want to pass lambdas that are not not convertible to a function pointer (eg if they capture something) you need something a bit more elaborate (note that you don't need to use std::function if you are only executing the lambda and not storing it). 如果你想将不可转换的lambda传递给函数指针(例如,如果它们捕获了某些东西),你需要更复杂的东西(请注意,如果你只是执行lambda,你不需要使用std::function而不是存储它)。

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

Now you can use the operator the way you envisioned: 现在您可以按照您设想的方式使用运算符:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM