K13>#include <concepts>
K13>template< typename T >
K13>concept cloneable = requires( T x )
K13>{
K13> { x.clone() } -> std::convertible_to<T*>;
K13>};
K13>template< cloneable T >
K13>struct OnlyCloneable {};
K13>struct Foo
K13>{
K13> Foo* clone() const { return new Foo(*this); }
K13> //using Ptr1 = OnlyCloneable<Foo>; // если раскомментарить, то ошибка компиляции
K13>};
K13>using Ptr2 = OnlyCloneable<Foo>;
K13>static_assert( cloneable<Foo> );
K13>int main()
K13>{
K13> return 0;
K13>}
K13>
K13>Вопрос -- это как-то лечится? K13>Почему проваливается проверка концепта внутри типа, хотя наличие метода clone компилятору уже известно?
Почему, понятно — потому что в этой точке Foo ещё не является полным типом. Примерно то же получится, если в этой точке попытаться полчучить sizeof(Foo). Выход сходу вижу только один — заводить Ptr1 внутрь функции-члена.
P.S. Возможно также, что это повод для того, чтобы пересмотреть дизайн на предмет избыточных и циклических зависимостей между типами.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, K13, Вы писали:
K13>Почему проваливается проверка концепта внутри типа, хотя наличие метода clone компилятору уже известно?
Судя по выхлопу clang-а компилятор считает Foo все еще незавершенным типом. И, в принципе, его можно понять, вдруг ниже будет еще одна перегрузка для clone.
Здравствуйте, K13, Вы писали:
K13>Вопрос -- это как-то лечится? K13>Почему проваливается проверка концепта внутри типа, хотя наличие метода clone компилятору уже известно?
Почему — уже пояснили выше. Полечить таки можно, если отложить инстанцирование концепта на потом. Например так https://gcc.godbolt.org/z/b9z5qs7rK
Здравствуйте, vopl, Вы писали:
V>Почему — уже пояснили выше. Полечить таки можно, если отложить инстанцирование концепта на потом. Например так V>https://gcc.godbolt.org/z/b9z5qs7rK
Так для использования снаружи нет и смысла заводить Ptr1 внутри Foo. Как я понял, в том-то и состоит подвох, что, вероятно, он хочет использовать Ptr1 внутри Foo, и не только внутри функций-членов.
--
Справедливость выше закона. А человечность выше справедливости.
Только закладывать в концепт наследование от конкретного класса — идея так себе, по-моему. Если на каждый концепт заводить по базовому классу — Cloneable, Printable, Loggagle, Serializable, Drawable, Sendable, Receivable, Insertable, Extractable... Программа превращается в зоопарк монстров. Концепты же для того и придумывались, чтоб этого всего не было. Чтоб можно было реализовывать обобщенные алгоритмы, применимые как пользовательским класам, так и к встроенными и библиотечным типам.
Я бы, все-таки, пошерстил дизайн на предмет циклических зависимостей.
--
Справедливость выше закона. А человечность выше справедливости.
лично у меня не скомпилировалось. Похоже derived_from требует полного типа.
R>Только закладывать в концепт наследование от конкретного класса — идея так себе, по-моему
я всегда их понимал как одно и тоже. В Rust трейты — фактически реализация наследования. Но в тоже время — это не что иное как концепты. В общем не вижу ничего плохого в смешении.
SP>лично у меня не скомпилировалось. Похоже derived_from требует полного типа.
А, ну да. Ну это и не принципиально. По семантике это, так или иначе, требование наследования.
SP>я всегда их понимал как одно и тоже. В Rust трейты — фактически реализация наследования. Но в тоже время — это не что иное как концепты. В общем не вижу ничего плохого в смешении.
В смешении в смысле совместного использования ничего страшного и нет. А вот когда в концепт заложено требование наследования — это сразу же наламывает принцип обобщённости. Взять тот же cloneable — почему бы мне не сделать клонируемыми, например, классы стандартных контейнеров, у которых типы элементов отвечают требованиям этого концепта? Ну да, для этого в концепте не следует закладываться и на функции-члены. Но это уже зло меньшего порядка, чем наследование, это легче исправить.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Я бы, все-таки, пошерстил дизайн на предмет циклических зависимостей.
Большая часть кода написана до c++11, и внутри многих классов определен тип Ptr,
который может быть и с подсчетом ссылок, и уникальным (тогда unique_ptr еще не было)
и Copy-on-Write если число владельцев (не читателей/писателей) больше 1...
Зато универсальный метод хранения любой сущности Foo -- в обертке Foo::Ptr,
где Ptr -- специализация одного из шаблонов типом Foo.
Варианты уникального и copy-on-write требуют наличия либо метода clone(), либо copy-ctor.
Первый -- для копирования, второй -- для доступа на запись, делать копию под эксклюзивное владение.
Придется оставить как было -- static_assert в теле метода.
Здравствуйте, K13, Вы писали:
K13>Варианты уникального и copy-on-write требуют наличия либо метода clone(), либо copy-ctor.
Тогда просто не используй концепт.
При отсутствии нужного clone() и так будет ошибка компиляции.
K13>Придется оставить как было -- static_assert в теле метода.
А если в коде clone по-факту не используется, то нафига?
=========
Меня, кстате, концепты этим немного и смущают, что они могут "требовать лишнего".
Они хороши для выражения ограничений, но менее хороши для утиной типизации, которой никакая помощь концептов не требуется — она и так отродясь в шаблонах была.