SO上有几个与将lambda投射到std::function
有关的问题,但我还没有看到一个使用参数包作为参数列表的问题。这似乎打破了我的g ++版本(7.1.1-4),可能它不受支持。这是合法的c ++ 17(按标准)吗?如果没有,为什么?
#include <functional>
template <typename TReturn, typename ... TArgs>
void Functor(std::function<TReturn (TArgs...)> f) {}
int main(int argc, char * argv[]) {
auto x = [] (int a, int b) { return a * b; };
Functor<int, int, int>(x);
return 0;
}
上面的代码无法编译,因为它的类型扣除失败。显然,明确地将x
键入std::function<int (int, int)>
而非使用auto
会导致错误消失。但这不允许我按照我的意愿将r值传递给Functor
。我还想通过使用函数类型的另一个模板参数来放弃任何类型安全。
我真正不明白为什么上面的代码无法编译,但下面的代码很好并且有效:
#include <functional>
template <typename TReturn, typename TArgA, typename TArgB>
void Functor(std::function<TReturn (TArgA, TArgB)> f) {}
int main(int argc, char * argv[]) {
auto x = [] (int a, int b) { return a * b; };
Functor<int, int, int> (x);
return 0;
}
答案 0 :(得分:10)
问题在于编译器不知道您希望int, int
成为整个TArgs
,因此尝试从参数中推导出TArgs
的余数f
。
例如,这是有效的:
Functor<int, int, int>(std::function<int(int, int, char, float)>{});
// TArgs := {int, int, [...] char, float}
因此,您需要指示编译器不要尝试推导TArgs
的剩余部分。例如,您可以写:
(*Functor<int, int, int>)(x);
或者您可以使用未分解的签名Functor
编写Sig
:
template <Sig>
void Functor(std::function<Sig> f) {}
或者您可以在非演推的上下文中将TArgs
的参数包含在参数f
中:
template <typename TReturn, typename ... TArgs>
void Functor(std::function<std::conditional_t<false, void, TReturn (TArgs...)>> f) {}
答案 1 :(得分:6)
这失败了:
#include <functional>
template <typename TReturn, typename ... TArgs>
void Functor(std::function<TReturn (TArgs...)> f) {}
int main(int argc, char * argv[]) {
auto x = [] (int a, int b) { return a * b; };
Functor<int, int, int>(x);
return 0;
}
因为您未指定TArgs...
的全部内容为{int, int}
。您正在做的是指定前两种类型是{int, int}
。实际上,通过提供这三种类型,我们将演绎问题转化为:
template <typename ... TArgs>
void Functor(std::function<int(int, int, TArgs...)> f) {}
int main(int argc, char * argv[]) {
auto x = [] (int a, int b) { return a * b; };
Functor(x);
return 0;
}
这不能编译,因为lambda不是std::function
(或从一个派生而来),这与你没有提供任何类型来调用它的原因相同从...开始。
非变量版本没有这个问题,因为您提供了所有类型。
但实际上,你想要的是:
template <typename F>
void Functor(F ) {}
这并不会让你失去任何类型的安全感。它使用std::function
丢失了类型信息,因为该类模板存在以键入erase。
答案 2 :(得分:2)
如果您要打电话给std::function
,将lambda强制转换为template
是一个不错的主意。 std::function
是类型擦除,模板类型擦除只有在你打算传递它时才有意义。在其他地方和/或返回它。
无论如何,试试这个:
template <class Sig>
void Functor(std::function<Sig> f) {}
int main(int argc, char * argv[]) {
auto x = [] (int a, int b) { return a * b; };
Functor<int(int, int)>(x);
return 0;
}
但你真的应该这样做
template <class F>
void Functor(F f) {}
这是完全类型安全的。
如果您想进行早期类型检查,可以编写
template<class Sig, class F>
struct signature_compatible;
template<class R, class...Args, class F>
struct signature_compatible<R(Args...), F> :
std::is_consructible< R, std::result_of_t<F(Args...)>>
{};
然后做
template <class Sig, class F>
void Functor(F f) {
static_assert( signature_compatible<Sig, F&>::value, "bad signature" );
}
但只有你真的需要。
答案 3 :(得分:0)
这是一个解决方案,可让您在不指定模板参数的情况下调用functor
:
#include <functional>
#include <type_traits>
template <class T> struct Fun_trait {};
template <class T, class... Args, class Ret>
struct Fun_trait<auto (T::*) (Args...) const -> Ret>
{
using F = std::function<auto (Args...) -> Ret>;
};
template <class TReturn, class... TArgs>
void functor(std::function<TReturn (TArgs...)> f) {}
template <class F>
std::void_t<decltype(&F::operator())>
functor(F f)
{
return functor<typename Fun_trait<decltype(&F::operator())>::F>(f);
};
int main(int argc, char * argv[])
{
auto x = [] (int a, int b) { return a * b; };
// nice and easy:
functor(x);
return 0;
}
这只是一个懒惰的初稿,可以帮助您入门。您需要将其展开以支持转发和非常量operator()
。
它有两个阶段:
我们有Fun_trait
谁 - 对于指针方法类型(例如lambda的operator()
) - 为所需的F
参数类型定义了别名std::function
。
接下来我们有一个函数functor
的重载,通过SFINAE std::void_t
仅针对具有非重载operator()
(例如lambda)的仿函数,然后使用上面的函数trait使用推导出的正确模板参数调用主函数functor
。