使用参数包

时间:2017-07-21 19:47:55

标签: c++ lambda variadic-templates c++17

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

4 个答案:

答案 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