C++ lambda表达式的模板推导

27

我有一个函数,它接受两个std :: function作为参数。第二个函数的参数类型与第一个函数的结果相同。

我编写了一个这样的函数模板:

template<typename ResultType>
void examplFunction(std::function<ResultType()> func, std::function<void(ResultType)> func2) {
  auto x = func();
  func2(x);
}

我可以这样调用它:

void f() {
  examplFunction<int>([]() { return 1; },   //
                      [](int v) { std::cout << "result is " << v << std::endl; });
}

有没有办法在examplFunction<int>中去掉<int>,让编译器推断出ResultType的类型?


9
Lambda表达式不是std::functionstd::function是一种类型擦除工具。类型推导和类型擦除是彼此相反的。如果您正在推断类型擦除工具的类型,那么几乎总是会出错。 - Yakk - Adam Nevraumont
4个回答

27

你实际上需要在那里使用 std::function 吗?std::function 在需要进行类型抹除时非常有用。使用模板时,通常可以完全跳过它:

template<class F1, class F2>
void examplFunction(F1 func, F2 func2, decltype(func2(func()))* sfinae = nullptr) {
  auto x = func();
  func2(x);
}

sfinae 参数确保该函数只能使用那些使得 func2 可以使用 func 的结果进行调用的函数。


2
请注意,您的SFINAE实际上与实现不对应。如果func返回一个移动语义类型(比如std::unique_ptr),那么SFINAE将允许它,但函数体将无法编译。示例 - Max Langhof
我想应该是这样的。我在想,如果使用隐式转换(还有一个重载的 func2),是否会导致它出现问题... 但我没有看到任何问题。 - Max Langhof
嗯,它看起来做了它应该做的事情。但是 decltype(func2(func()))* sfinae = nullptr 对我来说看起来很神奇。第三个函数参数如何限制第一和第二个参数? - ErWu
3
基本上,如果 func2(func()) 不符合语法规则,它会防止模板被实例化。SFINAE 是指从函数的重载集中删除候选函数以使程序能够继续编译。 - Angew is no longer proud of SO
@MatthieuM。不,没有理由。那只是我想到的第一件事情。一个带有默认参数的模板参数或者返回类型同样可以起作用。 - Angew is no longer proud of SO
显示剩余3条评论

13

有的。

template<typename ResultType>
void examplFunction_impl(std::function<ResultType()> func, std::function<void(ResultType)> func2) {
    auto x = func();
    func2(x);
}

template<class F1, class F2>
void examplFunction(F1&& f1, F2&& f2)
{
    using ResultType = decltype(f1());
    examplFunction_impl<ResultType>(std::forward<F1>(f1), std::forward<F2>(f2));
}

演示

在此情况中,您要求f1可以不带参数调用,以便在助手函数中确定返回类型。然后您使用显式指定返回类型的方式调用真实函数。

您可以添加一些SFINAE来确保此函数仅在f1确实可以像那样被调用(并且如果f2也可以使用f1的返回值进行调用)时才参与重载决议。

尽管我必须同意@Angew的看法,在给定的示例中没有必要使用std::function。当然,在实际情况下可能会有所不同。


3

std::function有一个模板(以及其他没有限制的)构造函数,因此仅从参数类型推导它并不是那么容易。如果这些参数仍需要是std :: function,您可以为两个std :: function的代价省略一个<int>,并让推导指南完成剩下的工作:

void f() {
    examplFunction(std::function([]() { return 1; }),   //
                   std::function([](int v) { std::cout << "result is "
                                             << v << std::endl; }));
}

有趣的是,并非总是有效。例如,libc++的当前实现缺少std::function的指导,因此违反了标准。

0

Angew的回答很好(应该被接受的答案),但缺少了检查section函数是否返回任何内容的细节。为了做到这一点,您需要使用std::is_void类型特征和std::enable_if:

template<class F1, class F2>
void examplFunction(F1 func, F2 func2, std::enable_if_t<std::is_void_v<decltype(func2(func()))>, void*> sfinae = nullptr) {
    auto x = func();
    func2(x);
}

如果你不熟悉类型特征和SFINAE,那么这种明显的写法会更冗长和难以阅读,所以如果你不需要确保F2返回void,那么它可能不是最好的选择。


1
OP的原始代码并不要求func2返回voidstd::function<void(ResultType)>可以从返回任何类型的函数中构造;它只会将返回值转换为void,也就是说,它会忽略它。std::function<void()>( []() { return 42; } )编译得很好 - Quuxplusone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接