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

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

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.

5 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

Другой (очень эффективный) способ - по умолчанию 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
}

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

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

Смотрите CppReference:

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

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

Параметр типа шаблона не может быть выведен из типа аргумента функции по умолчанию: шаблон 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)
}

Вы пытаетесь сказать то, что не имеет смысла. Вы просите компилятор угадать 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
}
Другие вопросы по тегам