Лямбдификация концепции - это улучшение или плохая практика?

Похоже, что вы можете добавить лямбду в концепцию, а затем написать в ней код. Возьмем это для примера. Я предпочитаю стандартные концепции для таких концепций и помню, что это только для целей этого примера - 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>;

Лямбдификация - это улучшение или плохая практика? С точки зрения удобочитаемости тоже.


person Dimitar Mirchev    schedule 20.08.2020    source источник
comment
Но, как показывает опубликованная вами ссылка, на самом деле это не работает. Если концепция терпит неудачу, вы получите серьезную ошибку, потому что код внутри лямбды создается.   -  person cigien    schedule 20.08.2020


Ответы (2)


Это не должно быть действительным. Суть разрешенных лямбда-выражений в неоцененных контекстах заключалась не в том, чтобы внезапно разрешить 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

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

person Barry    schedule 20.08.2020
comment
Спасибо! Это было то, чего мне не хватало, но мне было лень пойти и поискать это в стандарте. - person Dimitar Mirchev; 22.08.2020

Игнорируя очевидные недостатки этого механизма в удобочитаемости, он на самом деле не работает. Учтите следующее:

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

Но это работает только из-за особых правил для concepts. Скрытие требований в лямбдах не позволило бы этому сработать.

person Nicol Bolas    schedule 20.08.2020
comment
Спасибо! Больше того, чего мне не хватало. Очень признателен. Хотел бы я принять два ответа. - person Dimitar Mirchev; 22.08.2020