Можно ли использовать лямбды в качестве параметра шаблона, не являющегося типом?

Допустим ли следующий код?

template <auto Lambda>
struct A {};

int main () {
  auto lmb = [](int i){return i*i;};
  A<lmb> a;
  return 0;
}

Я заметил, что g ++ отлично его компилирует, а clang ++ возвращает error: a non-type template parameter cannot have type '(lambda at main.cpp:...)'.


person DarioP    schedule 11.06.2020    source источник
comment
gcc также принимает здесь constexpr auto= .... А как насчет других компиляторов? Только constexprs могут быть параметрами шаблона (включая auto).   -  person Sam Varshavchik    schedule 11.06.2020
comment
Согласно этой странице, clang еще не поддерживает типы классов как параметры шаблона, не являющиеся типами.   -  person interjay    schedule 11.06.2020
comment
... где GCC делает, начиная с GCC 9.   -  person dfrib    schedule 11.06.2020
comment
@Sam constexpr работает для лямбда-выражений без захвата, они явно являются типами Literl, начиная с C ++ 17, но перестают работать, когда вы захватываете что-то не-constexpr.   -  person n314159    schedule 11.06.2020
comment
@DarioP Был ли ответ на ваш вопрос? Я только что заметил, что вопрос все еще открыт.   -  person Ted Lyngmo    schedule 05.03.2021


Ответы (3)


Можно ли использовать лямбды в качестве параметра шаблона, не являющегося типом?

Да, с реализациями, в которых реализован P0732R2 - типы классов в параметрах шаблона, не являющиеся типами, но clang++ еще не реализовал его .

Источник: https://en.cppreference.com/w/cpp/compiler_support


Обратите внимание, что лямбда должна быть не менее constexpr (что по умолчанию):

Когда этот спецификатор отсутствует, оператор вызова функции в любом случае будет constexpr, если он удовлетворяет всем constexpr требованиям функции.

Однако вы можете добавить constexpr, чтобы получить ошибку в самой лямбде, а не при ее использовании в качестве параметра шаблона. В качестве примечания: вы также можете указать, что это consteval, чтобы он работал как параметр шаблона, не относящийся к типу.

Лямбда с отслеживанием состояния может быть constexpr:

constexpr auto lmb1 = [](int i) {
    static int x = 0;
    return i*i + ++x;
};

в то время как лямбда-захват по ссылке или захват копированием и изменением (mutable) не может. Захват копированием constexpr - это нормально.

Общие лямбды тоже могут быть constexpr:

constexpr auto gen_lmb = []<typename T>(T& val) {
   val += val;
   return val;
};

template <auto Lambda>
struct A {
    template<typename T>
    void doit(T&& arg) {
        std::cout << Lambda(arg) << '\n';
    }
};

//...

A<gen_lmb> ginst;

int v = 1000;
ginst.doit(v);
ginst.doit(std::string("foo "));
std::cout << v << '\n';
2000
foo foo
2000
person Ted Lyngmo    schedule 11.06.2020

[temp.arg.nontype] / 1:

Если тип T параметра шаблона содержит тип-заполнитель ([dcl.spec.auto]) или заполнитель для выведенного типа класса ([dcl.type.class.deduct]), типом параметра является тип выводится для переменной x в придуманном объявлении

T x = template-argument ;

Если выведенный тип параметра не разрешен для объявления параметра шаблона ([temp.param]), программа имеет неправильный формат.

Итак, правила устанавливаются [temp.param] / 6:

Параметр шаблона без типа должен иметь один из следующих типов (возможно, квалифицированных cv): ...

(6.1) структурный тип ...

Правила для структурного типа: - -Мой курсив -

(7.1) скалярный тип, или

(7.2) ссылочный тип lvalue, или

(7.3) тип буквального класса со следующими свойствами:

(7.3.1) все базовые классы и нестатические элементы данных являются общедоступными и неизменяемыми и

(7.3.2) типы всех базовых классов и нестатических элементов данных являются структурными типами или (возможно, многомерными) их массивами.

Поскольку у лямбда нет базового класса, единственное требование - он должен быть литералом тип класса ([basic.types]), который включает:

(10.5.2) ... тип закрытия ([ expr.prim.lambda.closure]) ...

Члены данных структурного типа также должны быть структурного типа, в данном случае это относится к захвату лямбда-выражения, если все его члены являются общедоступными и < em> неизменяемый.


@Nicol Bolas прокомментировал ниже, что лямбда с захватами, даже если захватывает тип литерала constexpr, стандарт не требует управления захватами как общедоступными полями.


Суть в том, что в C ++ 20 лямбда-выражение constexpr без захвата должно быть разрешено в качестве аргумента без типа шаблона (на основе [basic.types] /10.5.2, упомянутых выше).

См. Также ответ @Barry на аналогичный вопрос.


Код ниже компилируется с помощью gcc, но, как я понимаю из комментария Николаса Боласа , не все случаи гарантированы спецификацией (или, что еще хуже, все случаи не гарантированы спецификацией?).


Предположим, у нас есть:

template <auto T> struct A {};

struct B {};

struct C {
    ~C(){}
};

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

// compiles in gcc and should be ok by the spec as of [basic.types]/10.5.2
A<[](){}> a; // compiler deduces the anonymous lambda to be constexpr

auto lmb1 = [](){};
// same as above
A<lmb1> a1;

// compiler deduces lmb1 above to be constexpr
// same as it will deduce the following:
B b {};
A<b> here_i_am;

Лямбды, которые скомпилированы gcc в качестве аргументов шаблона, но, как утверждает в комментарии Никол Болас, спецификация не гарантирует, что они будут буквальными типами:

const int i = 0;
constexpr auto lmb2 = [i](){};
// compiles in gcc but is not guaranteed by the spec 
A<lmb2> a2;

constexpr auto lmb3 = [b](){}; // B is literal
// compiles in gcc but is not guaranteed by the spec 
A<lmb3> a3;

Лямбды нелитерального типа, недопустимые в качестве аргументов шаблона:

const int j = 0;
// below doesn't compile: <lambda()>{j} is not a constant expression
constexpr auto lmb4 = [&j](){}; // local reference - not constexpr
A<lmb4> a4;

C c;
// below doesn't compile: <lambda()>'{c} does not have 'constexpr' destructor
constexpr auto lmb5 = [c](){}; // C is not literal
A<lmb5> a5;
person Amir Kirsh    schedule 11.06.2020
comment
Ваш анализ приведет вас к правильному и наиболее интересному отрывку, является ли данная лямбда буквальным типом класса по состоянию на [basic.types] /10.5, но я думаю, что в ответе пропущены самые важные части (с учетом вопроса OP), а именно, когда лямбда явно не помечена как constexpr все еще относится к типу буквального класса? Например. when - неявно созданный деструктор лямбда-выражения ([expr.prim. lambda.closure] / 14) constexpr? - person dfrib; 11.06.2020
comment
или с типом литерала constexpr захватывает Нет, на самом деле. Как вы процитировали, строгое структурное равенство требует, чтобы члены были общедоступными. Ничто в стандарте не требует, чтобы захваты лямбда были общедоступными. Таким образом, лямбда захвата не требуется для использования в качестве NTTP. Кроме того, в стандарте нет ничего, что требовало бы, чтобы лямбда без захвата не имела членов. Это все еще может быть буквальный тип, но без явного утверждения о том, что лямбда без захвата должна иметь строгое структурное равенство, вы не можете на это полагаться. - person Nicol Bolas; 11.06.2020
comment
@NicolBolas Я только что узнал, что этот вопрос уже обсуждается в другом месте, с ответом @Barry, частичной цитатой: лямбды без захвата считаются как структурные типы ([temp.param] / 7) просто потому, что вообще не имеют каких-либо элементов данных. Мой скромный взгляд на это то же, что и у Барри. - person Amir Kirsh; 18.06.2020

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

Но тогда он должен работать в этом случае. Стандарт сообщает нам, что параметр шаблона без типа может быть буквальным тип класса (это немного сомнительно, поскольку замыкания являются буквальными, но на самом деле не типами классов, я думаю они явно включены здесь) с дополнительными требованиями, которые

  • Все базовые классы и нестатические члены должны быть неизменяемыми и общедоступными.
  • Их типы должны быть структурными или их массивами.

Итак, в этом простом примере у нас нет проблем. Но если вы что-то фиксируете, ваша лямбда имеет непубличную переменную-член и должна отсутствовать. Если это не так резко для замыканий, он определенно перестанет работать, если вы захватите что-то не constexpr.

person n314159    schedule 11.06.2020
comment
Лямбды по умолчанию constexpr начиная с c ++ 17 - person Richard Hodges; 11.06.2020