Подтвердить что ты не робот

Лямбда как аргумент по умолчанию не работает

Я получаю сообщение об ошибке с последними версиями 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();
            ^

Почему это не удается?

4b9b3361

Ответ 1

Вывод типа для auto не учитывает аргументы по умолчанию.

Ответ 2

Так как lambdas являются сахаром для функторов, проблема заключается в том, что функции шаблона не могут выводить аргументы шаблона (auto) в этом контексте по умолчанию.

Лямбда может быть сведена к структурному уровню функтора, принимая во внимание эти утверждения:

§5.1.2/3 [expr.prim.lambda]

Тип лямбда-выражения (который также является типом объекта замыкания) - это уникальный, неназванный тип неединичного класса [...]

§5.1.2/5 [expr.prim.lambda]

[...] Для общей лямбда тип закрытия имеет открытый шаблон участника-оператора встроенного функционального вызова (14.5.2), список шаблонов-параметров которого состоит из одного изобретенного типа template-parameter для каждого вхождения auto в параметре lambdas-declaration-clause, в порядке появления. [...]

Таким образом, тип вашей лямбда эквивалентен типу этого функтора:

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

И ваше использование затем эквивалентно:

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

Тип Auto1 не может быть выведен в этом контексте, как указано в §14.8.2.5/5 [temp.deduct.type]:

Невыводимые контексты:

[...]

- параметр шаблона, используемый в типе параметра параметра функции, который имеет аргумент по умолчанию который используется в вызове, для которого выполняется вывод аргумента.

Ответ 3

Функции шаблона (или методы) не выводят их параметры типа из своих аргументов по умолчанию, а замыкание с параметрами auto - это просто объект с методом шаблона.

Это делает наличие lambda по умолчанию для функции шаблона немного раздражающим.

Один из подходов заключался бы в том, чтобы набрать стирание, вызывающее объект, без его сохранения, например:

#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 не задействованы виртуальные таблицы или распределение кучи.

живой пример

Для не-лямбда вы можете сделать это:

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

который может быть проще оптимизировать.