19

使用以下代码,我收到最新版本的 clang 和 gcc 错误:

int main() {
    auto lambda = [] (auto = [] {}) {};
    lambda();
}

Clang给出错误:

prog.cc: In function 'int main()':
prog.cc:3:12: error: no match for call to '(main()::<lambda(auto:1)>) ()'
     lambda();
            ^
prog.cc:2:35: note: candidate: template<class auto:1> main()::<lambda(auto:1)>
     auto lambda = [] (auto = [] {}) {};
                                   ^
prog.cc:2:35: note:   template argument deduction/substitution failed:
prog.cc:3:12: note:   couldn't deduce template parameter 'auto:1'
     lambda();
            ^

为什么会失败?

4

3 回答 3

22

类型推导auto不考虑默认参数。

于 2015-05-14T14:42:33.097 回答
13

由于 lambda 是仿函数的,问题在于模板函数无法auto在此默认上下文中推导出模板参数 ()。

通过考虑以下语句,可以将 lambda 简化为函子结构级别:

§5.1.2/3 [expr.prim.lambda]

lambda 表达式的类型(也是闭包对象的类型)是唯一的、未命名的非联合类类型 [...]

§5.1.2/5 [expr.prim.lambda]

[...] 对于通用 lambda,闭包类型具有公共内联函数调用运算符成员模板(14.5.2),其模板参数列表由一个发明的类型模板参数组成,用于 lambda 参数中每次出现的 auto -declaration-clause,按出现顺序。[...]

因此,您的 lambda 类型等效于此函子类型:

struct unnamed
{
    template<typename Auto1>
    auto operator()(Auto1 = []{})
    {
    }
};

然后你的用法就相当于:

int main() {
    auto lambda = unnamed();
    lambda();
}

Auto1无法在§14.8.2.5/5 [temp.deduct.type]中指定的上下文中推断出的类型:

未推断的上下文是:

[...]

— 用于函数形参的形参类型中的模板形参,该形参有一个默认实参,该实参在进行实参推导的调用中使用。

于 2015-05-14T16:03:55.897 回答
2

模板函数(或方法)不会从它们的默认参数中推断出它们的类型参数,带auto参数的闭包仅仅是一个带有模板方法的对象。

这使得模板函数的默认 lambda 有点烦人。

一种方法是键入 erase 调用一个对象,而不存储它,如下所示:

#include <utility>
#include <type_traits>
#include <memory>

template<class Sig>
struct function_view;

template<class R, class...Args>
struct function_view<R(Args...)>{
  void* state;
  R(*f)(void*, Args&&...);

  template<class F, class=std::enable_if_t<std::is_convertible<std::result_of_t<F&(Args...)>,R>{}>>
  function_view( F&& fin ):
    state(const_cast<void*>(static_cast<void*>(std::addressof(fin)))),
    f( [](void* state, Args&&...args)->R{
      F&& f = std::forward<F>(*static_cast<std::decay_t<F>*>(state));
      return f(std::forward<Args>(args)...);
    })
  {}
  function_view( R(*fin)(Args...) ):
    state(fin),
    f( fin?+[](void* state, Args&&...args)->R{
      R(*f)(Args...) = static_cast<R(*)(Args...)>(state);
      return f(std::forward<Args>(args)...);
    }:nullptr)
  {}
  explicit operator bool(){return f;}
  function_view():state(nullptr),f(nullptr){}
  function_view(std::nullptr_t):function_view(){}
  R operator()(Args...args)const{
    return f(state, std::forward<Args>(args)...);
  }
};
template<class...Args>
struct function_view<void(Args...)>{
  void* state;
  void(*f)(void*, Args&&...);

  template<class F, class=std::result_of_t<F&(Args...)>>
  function_view( F&& fin ):
    state(const_cast<void*>(static_cast<void*>(std::addressof(fin)))),
    f( [](void* state, Args&&...args){
      F&& f = std::forward<F>(*static_cast<std::decay_t<F>*>(state));
      f(std::forward<Args>(args)...);
    })
  {}
  function_view( void(*fin)(Args...) ):
    state(fin),
    f( fin?+[](void* state, Args&&...args){
      void(*f)(Args...) = static_cast<void(*)(Args...)>(state);
      f(std::forward<Args>(args)...);
    }:nullptr)
  {}

  explicit operator bool(){return f;}
  function_view():state(nullptr),f(nullptr){}
  function_view(std::nullptr_t):function_view(){}
  void operator()(Args...args)const{
    f(state, std::forward<Args>(args)...);
  }
};

int main() {
  auto f = [] (function_view<void()> x=[]{}) {
    x();
  };
  f();
}

由于这仅适用于函数指针,而且我在 gcc 内联简单函数指针方面有很好的经验,因此它可能没有std::function. 与std::function不涉及虚拟表或堆分配不同。

活生生的例子

对于非 lambda,您可以这样做:

template<class X=function_view<void()>>
void f( X&& x=[]{} ) {
  x();
}

如果您传递,则推断是一个参数,如果您不传递,它将成为一个函数。你也可以这样做:

struct do_nothing {
  template<class...Args>
  void operator()(Args&&...)const{}
};

template<class X=do_nothing>
void f( X&& x=do_nothing{} ) {
  x();
}

这可能更容易优化。

于 2015-05-14T15:13:28.820 回答