函数模板是否可以在高阶函数调用中用作一等公民?

Can function templates be used as first class citizens in higher order function calls?

提问人:bitmask 提问时间:10/12/2022 更新时间:10/12/2022 访问量:155

问:

将函数模板作为参数传递给另一个函数模板总是有点棘手。通常,必须求助于创建一个 lambda 对象来调用原始函数。

template <typename It>
void f(It, It) {}

void g(std::vector<int>::iterator, std::vector<int>::iterator) {}

template <typename C, typename F>
void callA(C&& c, F callable) {
  return callable(std::begin(c), std::end(c));
}

问题

如果我有一个 ,我不能只传递给 因为是一个模板而不是一个函数:std::vector<int> cfcallAf

callA(c, f); // f has an unresolved overloaded function type and cannot be deduced
callA(c, std::distance); // same problem
callA(c, g); // works because g is a proper function, not a template
callA(c, [](auto a, auto b) {return f(a,b);}); // works again

即使我们帮助推断出可调用对象的类型:

template <typename C, template <typename> typename F,
          typename T = std::decay_t<decltype(std::begin(std::declval<C>()))>>
auto callB(C&& c, F<T> callable) {
  return callable(std::begin(c), std::end(c));
}

无法编译

问题

有没有办法强制 C++ 直接推断函数的类型,而无需诉诸 lambda 函数或类型擦除,例如?实际上,(如何)我才能将函数模板变成一等公民?也就是说,推断出它的类型。std::function

定义高阶函数时,我愿意花一些时间(参见 vs),但在调用它时却不行。callBcallA

C++ 模板 language-lawyer higher-order-functions first-class-functions

评论

0赞 lorro 10/12/2022
回复:(如何)我才能将函数模板变成一等公民?,正如您正确猜测的那样,通过 lambdas。函数重载和模板专用化可能因点而异(由于包含不同),因此需要明确地说,我在这里捕获它。
0赞 bitmask 10/12/2022
@lorro是的,但关键是强迫我将函数调用包装在 lambda 中意味着该函数不是“一等公民”。它是二等舱。
0赞 songyuanyao 10/12/2022
您可以使用函数指针作为参数吗?可以从函数模板转换。
0赞 bitmask 10/12/2022
@songyuanyao 当然,但是如何获得正确的函数指针呢?
0赞 lorro 10/12/2022
@bitmask 我要说的是,它根本不是公民。考虑一下,在 a.cpp 中,您会看到模板专用化,而在 b.cpp 中看不到它。捕获应该是什么?它因地点而异。

答:

3赞 songyuanyao 10/12/2022 #1

您可以将参数类型更改为函数指针,可以从函数模板转换(推导模板参数)。例如

template <typename C>
void callA(C&& c, void(*callable) (std::decay_t<decltype(std::begin(std::declval<C>()))>, std::decay_t<decltype(std::begin(std::declval<C>()))>)) {
  return callable(std::begin(c), std::end(c));
}

然后

std::vector<int> c;
callA(c, f); // template argument It will be deduced as std::vector<int>::iterator for f

评论

0赞 n. m. could be an AI 10/12/2022
它不适用于非函数可调用对象,例如捕获 lambda 或 ,但这可以通过简单地将原始 OP 添加为重载来修复。std::functioncallA
0赞 bitmask 10/12/2022
这似乎工作得很好,只是必须修复可调用对象的返回类型。因此,像 which return void 这样的函数需要与 .fcallAstd::distance
0赞 n. m. could be an AI 10/12/2022
@bitmask 这不是必需的,您可以为返回类型添加另一个模板参数
0赞 bitmask 10/12/2022
@n.1.8e9-where's-my-sharem。我无法让推论起作用。我可以让它工作,这样你就可以说和(默认为 )。但我看不出有办法自动开始工作。callA<std::ptrdiff_t>(a, std::distance)callA(a, f)voidcallA(a, std::distance)
0赞 n. m. could be an AI 10/12/2022
@bitmask嗯,没错,推理是行不通的。
1赞 Enlico 10/12/2022 #2

你知道BOOST_HOF_LIFT吗?

它允许您使用与 一样简单的语法将重载和/或模板化的函数提升到对象中。auto my_max = BOOST_HOF_LIFT(std::max);

您可以更改为 .callA(c, std::distance)callA(c, BOOST_HOF_LIFT(std::distance))

评论

0赞 Caleth 10/12/2022
lift 是一个宏,用于定义模板周围的 lambda
0赞 Enlico 10/12/2022
@Caleth,我知道,但恕我直言,不必给自己写 lambda 是一件好事。我的意思是范围循环被脱糖成一个“正常”循环,但它不是一个有用的抽象吗?forfor