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

Aug 20 2020

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

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

Ответы

2 Barry Aug 21 2020 at 01:10

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

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

5 NicolBolas Aug 20 2020 at 21:58

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

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