Лямбдификация концепции - это улучшение или плохая практика?
Похоже, что вы можете добавить лямбду в концепцию, а затем написать в ней код. Возьмем это для примера. Я предпочитаю стандартные концепции для таких концепций и помню, что это только для целей этого примера - godbolt
template<class T>
concept labdified_concept =
requires {
[](){
T t, tt; // default constructible
T ttt{t}; // copy constructible
tt = t; //copy assignable
tt = std::move(t); // move assignable
};
};
Вместо:
template<class T>
concept normal_concept =
std::default_initializable<T> && std::movable<T> && std::copy_constructible<T>;
Лямбдификация - это улучшение или плохая практика? С точки зрения читабельности тоже.
Ответы
Это не должно быть действительным. Суть разрешенных лямбда-выражений в неоцененных контекстах заключалась не в том, чтобы внезапно разрешить SFINAE для операторов.
В [temp.deduct] / 9 есть формулировка, которая поясняет это:
Лямбда-выражение появляется в типе функции или параметре шаблона не рассматривается как часть непосредственного контекста для целей аргументов шаблона вывода. [ Примечание : цель состоит в том, чтобы избежать требования, чтобы реализации имели дело с ошибкой подстановки, связанной с произвольными операторами. [ Пример :
template <class T> auto f(T) -> decltype([]() { T::invalid; } ()); void f(...); f(0); // error: invalid expression not part of the immediate context template <class T, std::size_t = sizeof([]() { T::invalid; })> void g(T); void g(...); g(0); // error: invalid expression not part of the immediate context template <class T> auto h(T) -> decltype([x = T::invalid]() { }); void h(...); h(0); // error: invalid expression not part of the immediate context template <class T> auto i(T) -> decltype([]() -> typename T::invalid { }); void i(...); i(0); // error: invalid expression not part of the immediate context template <class T> auto j(T t) -> decltype([](auto x) -> decltype(x.invalid) { } (t)); // #1 void j(...); // #2 j(0); // deduction fails on #1, calls #2
- конечный пример ] - конечное примечание ]
У нас просто нет ничего эквивалентного по требованиям. gcc действительно то, что вы ожидаете:
template <typename T> concept C = requires { []{ T t; }; };
struct X { X(int); };
static_assert(!C<X>); // ill-formed
Поскольку тело лямбды находится вне непосредственного контекста, это не ошибка замены, а серьезная ошибка.
Игнорируя очевидные недостатки этого механизма, на самом деле он не работает . Обратите внимание на следующее:
template<labdified_concept T>
void foo(T t) {}
template<typename T>
void foo(T t) {}
Правила концепций говорят нам, что если данное T
не удовлетворяет labdified_concept
, то foo
вместо этого следует создать экземпляр другого . Но этого не произойдет, если мы предоставим SS
такой шаблон. Вместо этого мы получаем серьезную ошибку, потому что labdified_concept<SS>
не может быть создан.
Материал внутри requires
выражения имеет специальную обработку, которая позволяет рассматривать определенные типы ошибок как сбои в выполнении требований. Но эта обработка не применяется к телу лямбды. Здесь неправильно сформированный код имеет неправильный формат, и поэтому вы получаете ошибку компиляции при попытке создать его экземпляр.
И даже если это сработало, все равно не работает. У концептов есть сложные правила для определения понятий, что позволяет считать разные концепции более узкоспециализированными, чем другие. Это позволяет перегрузить различные концепции , что позволяет вызывать более ограниченную концепцию. Например, концепция, которая требует только, default_initializable
является более общей, чем концепция, которая требует default_initializable
и moveable
. Таким образом, если тип удовлетворяет обоим параметрам, будет выбран последний, поскольку он более ограничен.
Но это работает только из-за особых правил для concept
s. Скрытие требований в лямбдах не позволило бы этому сработать.