Лямбда по умолчанию как шаблонный параметр функции

Рассмотрим следующий код

template<bool b, typename T> void foo(const T& t = []() {}) {
  // implementation here
}

void bar() {
  foo<true>([&](){ /* implementation here */ }); // this compiles
  foo<true>(); // this doesn't compile
}

В случае, если он не компилируется, я получаю следующие ошибки:

error C2672: 'foo': no matching overloaded function found
error C2783: 'void foo(const T&)': could not deduce template argument for 'T'

Думаю, понятно, чего я хочу добиться: пусть foo вызывается с лямбдой, предоставляемой клиентом, и без нее. Компилятор представляет собой набор инструментов MSVC++2017 версии 15.4.4 v141.


person Serge Rogatch    schedule 26.11.2017    source источник


Ответы (5)


Компилятор использует переданные аргументы для определения типа шаблона. Если аргументов нет, то как компилятор сможет вывести тип шаблона?

Здесь вы можете использовать перегрузку вместо аргументов по умолчанию.

Перегруженная функция без аргументов может просто вызвать функцию с аргументом «по умолчанию»:

template<bool b, typename T> void foo(const T& t) {
  // implementation here
}

template<bool b> void foo() {
  foo<b>([]() {});
}
person Some programmer dude    schedule 26.11.2017
comment
Первый абзац не имеет особого смысла. Явных аргументов может и не быть, но компилятор может использовать аргументы по умолчанию, как если бы они были указаны на месте вызова; стандарт делает все возможное, чтобы исключить этот случай, что говорит о том, что не так уж неразумно ожидать, что он сработает. Я подозреваю, что настоящий виновник здесь заключается в том, что добавление этой дополнительной гибкости еще больше усложнит процесс разрешения перегрузок, а это уже достаточно херня. - person Matteo Italia; 26.11.2017

Аргументы функции по умолчанию не являются частью процесса вывода аргументов шаблона. Цитировать [temp.deduct.partial]/3 :

Типы, используемые для определения упорядочения, зависят от контекста, в котором выполняется частичное упорядочение:

  • В контексте вызова функции используемые типы — это те типы параметров функции, для которых вызов функции имеет аргументы. 141

141) Аргументы по умолчанию не считаются аргументами в этом контексте; они становятся аргументами только после выбора функции.

Этот маркер и примечание указывают на то, что, поскольку вы не предоставили аргумент для t в вызове foo, тип T не может быть выведен. Аргумент лямбда по умолчанию может быть принят во внимание только в том случае, если функция выбрана для вызова, а не раньше.

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

person StoryTeller - Unslander Monica    schedule 26.11.2017

Другой (очень эффективный) способ - по умолчанию T является нулевым функтором.

// no_op is a function object which does nothing, regardless of how many
// arguments you give it. It will be elided completely unless you compile with
// -O0
struct no_op 
{ 
    template<class...Args>
    constexpr void operator()(Args&&...) const {} 
};

// foo defaults to using a default-constructed no_op as its function object
template<bool b, typename T = no_op> void foo(T&& t = T()) 
{    
  // implementation here
    t();
}

void bar() {
  foo<true>([&](){ std::cout << "something\n"; }); // this compiles
  foo<true>(); // this now compiles
}
person Richard Hodges    schedule 26.11.2017
comment
ИМХО, это лучше, чем принятый ответ. Другим читателям: пожалуйста, не думайте, что этот ответ слишком длинный. Последние строки (bar()) просто тестируются. - person cppBeginner; 10.08.2020

Попробуйте перегрузить его напрямую:

template <bool b>
void foo(void) {
  foo([](){});
}

См. CppReference:

Невыведенные контексты

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

Параметр шаблона типа не может быть выведен из типа аргумента функции по умолчанию: template void f(T = 5, T = 7);

void g()
{
    f(1);     // OK: calls f<int>(1, 7)
    f();      // error: cannot deduce T
    f<int>(); // OK: calls f<int>(5, 7)
}
person iBug    schedule 26.11.2017
comment
Я не уверен, что это необходимо для работы - я не думаю, что стандарт предписывает, чтобы два экземпляра одной и той же лямбды имели один и тот же тип. - person Matteo Italia; 26.11.2017

Вы пытаетесь сказать что-то бессмысленное. Вы просите компилятор угадать T из ваших аргументов, но не предоставляете никаких аргументов.

Следующий код компилируется и делает то, что вы хотите:

template<bool b, typename T> void foo(const T& t) {
  // implementation here
}

template<bool b> void foo() {
  foo<b>([]() {}); // Call actual implementation with empty lambda
}

void bar() {
  foo<true>([&](){ /* implementation here */ }); // this compiles
  foo<true>(); // this now compiles as well
}
person Shachar Shemesh    schedule 26.11.2017