为什么非捕获lambda不能进行默认构造,有没有办法解决这个问题?

5

可以将lambda的类型用作模板参数,例如:

template<typename InArg, typename Function>
class selfCompose {
  Function f;
 public:
  selfCompose(Function f): f(f) {}
  auto operator() (InArg x) -> decltype(f(f(x))) {
    return f(f(x));                              }
};

int main() {
  auto f = [](int x){return x*x;};
  std::cout << selfCompose<int, decltype(f)>(f)(4)  //  yields (4²)² = 256
            << std::endl;
  return 0;
}

然而,这里对f的双重使用有点冗余。我们可以省略传递lambda的类型作为模板(将其转换为适当的std::function(损失多态性 - 但是C++ lambda无法参数化多态)),但我有一个应用程序,我更喜欢不必将其传递给构造函数(因为我想使用我的类的初始化本身作为模板参数,在预期特定构造函数签名的情况下)。我希望它能像这样工作:
template<class InArg, class Function>
class selfCompose {
  Function f;
 public:
  selfCompose() {}  // default constructor for f
  auto operator() (InArg x) -> decltype(f(f(x))) {
    return f(f(x));                              }
};

int main() {
  auto f = [](int x){return x*x;};
  std::cout << selfCompose<int, decltype(f)>()(4) << std::endl;
  return 0;
}

但是这段代码不能编译,因为Lambda表达式有一个已删除的默认构造函数。对于捕获lambda来说这是不可避免的,但对于像我例子中的简单lambda来说,这对我来说没有太多意义:它们不需要引用任何本地变量。
是否有其他方法可以获得这种功能,还是我必须采用将lambda定义为命名类的老式方法?
struct myFun {
  auto operator() (int x) -> int {return x*x;}
};

当然,我想要使用的Lambda函数并不像x → x²那么简单,因此仅从几个标准函数类中选择不够灵活。
3个回答

5
您可以按照类似 make_pairmake_shared 函数的示例进行操作:
template<typename InArg, typename Function>
selfCompose<InArg, Function> make_selfCompose(Function f)
{
  return selfCompose<InArg, decltype(f)>(f);
}

int main() {
  auto f = [](int x){return x*x;};
  std::cout << make_selfCompose<int>(f)(4)
            << std::endl;
  return 0;
}

@Nawaz 不是很对。我的lambda的明显实现只是一个无状态类,其中有一个成员 operator() - leftaroundabout
@leftaroundabout:“当然”?此外,成员函数指针不能转换为函数指针。此外,标准不要求lambda是类! - Nawaz
@Nawaz,你能把你的想法写成一个答案吗?我觉得我明白了重点。 - leftaroundabout
@leftaroundabout:所有实例都相等并不意味着有唯一的实例。考虑 struct X {}; X a,b,c; 所有的 X 实例等效(具有相同的行为),但不是同一个(具有不同的标识)。我不太明白为什么您不想将lambda作为参数传递,特别是compose<decltype(f)>(5)compose(f)(5)之间有什么不同(我实际上觉得第二个更容易读)。如果您担心可能的优化问题,请不要,编译器知道该做什么。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas 说得对:它们是等价的,这在我的应用程序中完全没问题。——我不担心性能,只是想将类型可比较的selfCompose<int, decltype(f)>作为模板参数传递给现有类,该类本身将实例化多个不同的参数类实例,并未准备好向构造函数传递任何额外的参数。 - leftaroundabout
显示剩余5条评论

1
如果将selfCompose变成多态的,就不需要显式传递参数类型或检查存储的函数对象类型。后者意味着它也能处理多态函数对象。
template<typename Functor>
struct self_compose_type {
    // Omitting encapsulation for brevity
    Functor functor;

    // It is possible to overload operator() to deal
    // with all cv and ref qualifiers combinations
    template<typename... T>
    auto operator()(T&&... t)
    // Don't use std::result_of for the return type
    -> decltype( std::declval<Functor&>()(std::declval<T>()...) )
    { return functor(std::forward<T>(t)...); }
};

template<typename Functor>
self_compose_type<Functor>
self_compose(Functor&& functor)
{ return { std::forward<Functor>(functor) }; }

// C++03 style:
template<typename Functor>
self_compose_type<typename std::decay<Functor>::type>
make_self_compose(Functor&& functor)
{ return { std::forward<Functor>(functor) }; }

没有必要让operator()成为变参函数,如果你愿意的话,可以使其只接受一个参数。


-2

Benjamin已经为您的情况发布了一个不错的解决方法。您可以使用它。

我的解决方案只是Benjamin答案的改进版本,您不必指定lambda参数type。因此,您可以这样编写:

make_selfCompose<int>(f)(4); //Benjamin's solution

你可以只写这个:

make_selfCompose(f)(4); //improved - absence `int` template argument.

make_selfCompose自己推断lambda参数类型。

为了实现这个解决方案,让我们首先编写function_traits类模板:

#include <tuple>

template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{};

template <typename C, typename R, typename... A>
struct function_traits<R(C::*)(A...) const>
{
   template <size_t i>
   struct arg
   {
      typedef typename std::tuple_element<i, std::tuple<A...>>::type type;
   };
};

那么这里是make_selfCompose的改良版本:

template<typename Fun> //<--- now it has one template parameter
selfCompose<typename function_traits<Fun>::template arg<0>::type, Fun>
make_selfCompose(Fun f)
{
  typedef typename function_traits<Fun>::template arg<0>::type InArg; //deduce it
  return selfCompose<InArg, decltype(f)>(f);
}

这是一个测试程序:

int main() {
  auto f = [](int x){return x*x;};
  std::cout << make_selfCompose(f)(4)  //notice the relief here!
            << std::endl;
  return 0;
}

请查看在线演示

希望能有所帮助。:-)


1
说实话,标准要求无状态的lambda可以轻松构造,并且仍然可以转换为指向函数的指针并不难。然后 [](){}->void 就将成为 struct Stateless { static void doop() {}; void operator()() const {doop();}; operator void(*)()() const { return &Stateless::doop; }; }; 或者是标准中的等价物。据我所知,这与几乎所有符合标准的C++11代码都是一致的(除了某些能够检测到已删除的构造函数的代码)。 - Yakk - Adam Nevraumont
@Yakk:但事实是标准并不要求这样做。我并不是说这不可能,只是这不是标准的C++要求。 - Nawaz
6
“lambda表达式的类型不一定是类。”——这是错误的,它必须是一个独特的、未命名的非联合类类型(见§5.1.2/3)。同时,它还必须重载operator()(同一条款,第5段)。 - Xeo
2
事实上,标准要求无状态的lambda表达式应该可以转换为指向函数的指针。因此,无状态的lambda表达式在功能上类似于自由函数,而不是类。总之这句话没有任何逻辑关联。就像Xeo所指出的那样,实际情况恰恰相反:它必须是一个类类型。 - GManNickG
@Xeo:谢谢您提供的信息。我已从我的答案中删除了不正确的部分。 - Nawaz
显示剩余6条评论

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