Comments 84
То есть:
а) теперь надо знать этот мета-язык, который уже отдаляется от С++;
б) любой библиотеко-писатель сможет под себя таких правил наворотить, что знание всех деталей стандарта собственно языка С++ уже не поможет разобраться, что же делает (и даже что означает) этот код.
Я не против нововведений, но это как-то странно.
Больше похоже на кодогенерацию.
Да это же просто reflection времени компиляции, как во многих других языках. Не должно случиться ничего страшного
Хорошее вступление для постапокалиптического фильма-боевика.
Хорошее вступление для постапокалиптического фильма-боевика.
Видеоряд во вступлении должен иметь на правой части седого, морщинистого генерала или адмирала, на фоне тихоокеанского заката, со скептическим прищуром выслушивающего эти заверения, исходящие от бывшего аспирантика (слева) с факультета искусственного интеллекта боевых робототехнических систем...
И каким же образом интерфейсы могут разруливаться на этапе компиляции? Особенно вот в таком коде:
object a = ...;
IFoo b = (IFoo)a;
b.Bar();
IFoo* a = new MyFoo();
a->Bar();
JIT умеет делать оптимизацию «девиртуализация», но это JITJIT в данном случае может сделать не больше, чем статический компилятор.
Runtime-статистика не поможет. Ведь, если в 1000 случаев по ссылке IFoo пришёл объект MyFoo, то это не гарантирует, что в 1001-й раз не придёт объект YourFoo.
Стандартный трюк JIT (сделаем простую проверку и, если угадали, запускаем оптимизированный под частый случай код, а если не угадали — универсальный, но медленный код) тут не поможет, т.к. проверка принадлежности объекта классу + невиртуальный вызов тяжелее, чем сразу виртуальный вызов без проверки.
Вообще-то может из-за наличия class loaders. Объект YouFoo гарантированно не может придти если такой класс не загружен.
Я не уверен что все работает именно так, но при загрузке новых классов точно перекомпилируются некоторые методы. И логично при этом посчитать количество реализаций интерфейса и избавится от виртуального вызова (если 1) или заменить на switch-case (если мало).
Кстати, что-то похожее (я имею в виду мета-код) есть в языке Nemerle, который, к сожалению, так и не получил распространения (хотя может и к счастью).
Ну, нет, ну только не $ в плюсах.
С другой стороны, примеры натянуты. Профит от интерфейсов как отдельной сущности неясен, в том же шарпе это более безопасный механизм для множественного наследования, в С++ оно и так работает для чего угодно и искусственные ограничения особо ничего не изменят. В примере с точкой: во-первых, достаточно оператора <. Во-вторых, есть пары/tuple, которые хоть и грязновато, но делают то же самое. В-третьих, структуры часто не настолько тривиальны и вам может быть нужно, например, сравнивать только 2 поля из 3х или же сравнивать поля в разных направлениях итд. И самое страшное случится, когда это надо не на стадии дизайна структуры, а уже потом. И придется вам убирать этот ordered и писать весь код сравнения заново.
Так что хотелось бы увидеть примеры чего-то из Qt новыми средствами ну или вообще что-нибудь такое, что сейчас в языке сделать нельзя/делается крайне неудобно.
using byte = uint8_t;
$class serializable
{
std::vector<byte> serialize()
{
std::vector<byte> ret;
for (auto o : serializable.variables()) {
if (o.is_class<serializable>::value) {
ret.append(o.serialize());
}
else {
compiler.require(!std::is_trivial<typeof(o)>::value,
"a value type must be either trivial or serializable");
ret.append(reinterpret_cast<byte*>(&o), sizeof(o));
} }
return ret;
}
void unserialize(std::vector<byte> v, size_t &offset)
{
std::vector<byte> ret;
for (auto o : serializable.variables()) {
if (o.is_class<serializable>::value) {
o.unserialize(v, offset);
}
else {
compiler.require(!std::is_trivial<typeof(o)>::value,
"a value type must be either trivial or serializable");
memcpy(&o, sizeof(o), v.data(), offset);
offset += sizeof(o);
} } }
};
и просто прописывать
serializable Point3f
{
float x, y, z;
};
serializable Mesh
{
Point3f a, b, c;
Point3f texa, texb, texc;
};
int main() {
std::vector<byte> v = magic(of_puberty);
size_t offset = 0;
Mesh mesh;
mesh.unserialize(v, offset);
return 0;
}
И от макроса Q_OBJECT можно было бы избавиться. А если можно будет определять мета-методы или мета-секции (или определять свои модификаторы аля __declspec), то даже не придётся париться по поводу синтаксиса сигналов и слотов — оно само собой разрешится.
По поводу вашего примера: выглядит прикольно, но на деле костыль, имхо. Самое важное — нельзя же сделать, например, ordered serializable? Во-вторых, неясно, как вы, например, контейнеры будете сериализовать? (я так понимаю — никак)
Dark_Daiver
Лучше бы уж сразу дали работу с AST из cpp кода. Чтобы самому можно было написать аналог «deriving», например.Собственно, что мета-уровень в описанном виде, что доступ к AST-дереву должны иметь какой-либо синтаксис. У нас есть два варианта: расширение синтаксиса шаблонов или новый. Оба варианта плохи. Введение нового синтаксиса даст ЧЕТВЁТРЫЙ язык программирования внутри плюсов, пополнив непосредственно плюсы, макросы и шаблоны. Встаёт вопрос, куда его впихнуть в этапе компиляции, к примеру, да и много чего по совместимости. Шаблоны уже сейчас выглядят ХТОНИЧЕСКОЙ МАГИЕЙ, которую можно написать лишь единожды, и их, наоборот, стоило бы разгрузить от ненужных нагромождений. Возможно, кстати, введение простого систаксиса доступа к AST параметрам без выведения кучи новых шаблонов дало бы нам вздохнуть спокойно (Variable.is_const() вместо std::is_const::value смотрится, имхо, куда плюсовее).
Что до trait'а — всё так. Можно было бы обойтись std::serialize<>, если бы.
daiver19
Я собственно и попросил пример из Qt или чего-то еще, где это незаменимо. Я не говорю, что это не нужно, мне просто не понравились примеры из статьи.Qt6, судя по рассказам разработчиков, уходит в шаблоны. В общем, для отказа от moc не хватает доступа к AST в любом виде.
struct qt_meta_stringdata<$object> {
constexpr {
for... (auto method : $object.methods()) {
if (method.hasProperty("slot")) {
QT_MOC_ADD_LITERAL(method.name());
} } }
};
struct qt_slot_property {
uint name, argc, parameters, tag, flags;
};
struct qt_slot_properties<$object> {
uint ammount;
std::array<qt_slot_property,ammount> properties;
constexpr {
ammount = std::count_if<
$object.methods(),
[](auto& method)->bool{return method.hasProperty("slot");}
>();
properties = std::make_array_if_impl<
$object.methods(),
[](auto& method) {return method.hasProperty("slot");},
[](auto& method) {return qt_slot_property prop(QT_MOC_GET_LITERAL_INDEX(method.name()),method.arg().size(), ...);}
>();
}
}
};
$class object {
/* ... */
static QMetaTypes qt_meta_types;
static const qt_meta_stringdata<$object> qt_meta_stringdata_$$object;
static const qt_slot_properties<$object> qt_meta_data_$$object;
static const std::array <$object::method_type> qt_meta_vtable_$$object;
void qt_static_metacall(object *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
auto args = qt_make_arg_list(qt_meta_vtable_$object[_id], _a);
qt_meta_vtable_$object[_id].invoke(args);
} }
constexpr {
qt_meta_types.append(meta_type($object));
}
};
Я собственно и попросил пример из Qt
Этот пример есть в документе по ссылке. Qt-код будет выглядеть вот так:
QClass MyClass {
property<int> value { };
signal mySignal();
slot mySlot();
};
QClass, property, signal и slot будут определены разработчиками Qt как метаклассы со своими правилами генерации соответствующих методов и всё это будет компилится любым компилятором, без всякого moc.
«Свои модификаторы аля __declspec» в языке уже есть, см. attribute sequence. По стандарту, компилятор обязан игнорировать атрибуты, которые он не поддерживает.
Это решение с изъянами, и оно не позволит в дальнейшем сделать что-то другое потому что «такой синтаксис уже используется»
В derive Rustа можно выводить несколько сущностей, лишь бы они не конфликтовали. А здесь только одну.
Я не профессиональный программист, я инженер, в круг интересов которого программирование входит, и для меня подобные вещи превращают программирование в шаманство. Я всегда относился к языку программирования как к инструменту вычислений (порой очень запутанных) и обслуживания этих вычислений. Последнее время, читая подобные статьи, не перестаю удивляться как далеко зашли требования профессионалов к ЯП, насколько сильно развилась мысль и какие конструкции в языках, необходимы для реализации этих требований.
После того как я увидел как в компиляторе https://cppx.godbolt.org/ уже работает рефлексия:
struct { int x,y; } p;
// получаем количество переменных в структуре переменной (без предкомпиляции, шаблонов и библиотек):
сonstexpr size_t a = $decltype(p).variables().size(); // mov qword ptr [rbp - 32], 2
И сравнив с огородом шаблонов, которые практически невозможно отлаживать, в своем проекте, мне захотелось взять этот транк clang, и использовать в продуктиве, настолько это круто и просто.
Ну, для вас ни Fortran, ни C никто не отменял.
Хотя сама идея метаклассов хорошая, но она больше для языков с duck typing. Для Python, например.
Точно не надо разрешать вводить новые «ключевые слова». Хотя бы так уже:
class MyClass: meta MyMetaClass {}
class MyVector {
// ...
private:
MyVectorPrivate *_d;
}
Компилятор очевидно будет не прав, если сгенерирует оператор сравнения векторов как lhs._d == rhs._d.
Зато с концептами возможен путь от обратного: класс сравниваем (EquallyComparable), если определен оператор ==, возвращающий bool (requires (T a, T b) { {a == b} -> bool; };)
Приведу пример. С концептами/ренджами и uniform call syntax, вашему MyVector достаточно реализовать методы begin(), возвращающий указатель на начало и end(), возвращающий указатель за конец массива и о чудо! MyVector становится контейнером, для которого определен весь стандартный набор алгоритмов над диапазонами (например, sort, если тип, возвращаемый *begin(), поддерживает оператор <).
Есть еще propolsal по operator «spaceship» <=> — один метод, реализующий весь набор функций сравнения.
$class comparable {
friend bool operator == (const comparable& a, const comparable& b) {
constexpr {
for... (auto o : comparable.variables()) {
compiler.require(o$.isPointer(),
"Неоднозначное сравнение!");
-> { if (!(a.(o.name)$ == b.(o.name)$)) return false; }
}
}
return true;
}
};
$class pointer_comparable : comparable {
friend bool operator == (const pointer_comparable& a, const pointer_comparable& b) {
constexpr {
for... (auto o : pointer_comparable.variables()) {
-> { if (!(a.(o.name)$ == b.(o.name)$)) return false; }
}
return true;
}
}
};
$class value_comparable : comparable {
friend bool operator == (const value_comparable& a, const value_comparable& b) {
constexpr {
for... (auto o : value_comparable.variables()) {
if (!o.isPointer()) -> { if (!(a.(o.name)$ == b.(o.name)$)) return false; }
else { if (!(*a.(o.name)$ == *b.(o.name)$)) return false; }
}
}
return true;
}
};
$class no_pointer_comparable : comparable {
friend bool operator == (const no_pointer_comparable& a, const no_pointer_comparable& b) {
constexpr {
for... (auto o : no_pointer_comparable.variables()) {
if (!o.isPointer()) -> { if (!(a.(o.name)$ == b.(o.name)$)) return false; }
}
return true;
}
}
};
template <value_comparable T>
comparable TreeNode {
using ParentNode = (TreeNode*).as(pointer_comparable);
using ChildNode = (TreeNode*).as(no_pointer_comparable);
ParentNode parent;
std::set<ChildNode> children;
T value;
};
Приведу пример. С концептами/ренджами и uniform call syntax, вашему MyVector достаточно реализовать методы begin(), возвращающий указатель на начало и end(), возвращающий указатель за конец массива и о чудо!Окей, я сделал односвязный список. Заведётся? Ну-ну…
Вместе с begin и end нужно определить iterator, разделяя при этом forward_iterator и backward_iterator. В
template <class T>
interface iterator {
T& operator*();
};
interface forward_iterator : iterator {
forward_iterator & operator++();
};
interface backward_iterator : iterator {
forward_iterator & operator--();
};
container MyArray {
using iterator_type = (value*).as(forward_iterator, backward_iterator);
iterator_type begin();
iterator_type end();
};
container MyList {
struct node { node * next; value val; /*...*/ };
using iterator_type = (node*).as(forward_iterator);
iterator_type begin();
iterator_type end();
};
2. для нетривиальной итерации да, придется определить итератор. Но зачем их разделять? Определил методы */+±- вуаля, у нас bidirectional_iterator. Определил методы +(Integral)/-(Integral) — вуаля, у нас random_access_iterator. В вашем случае всё равно придется определять операторы */+±- для итератора. В итоге всё то же самое, но с дополнительными действиями.
Если же вы имеете ввиду дуализм указателей, *pointer[], то это решается через
$class pointer { operator[]() = delete; } и $class array { operator*() = delete; }. Очень интересно, как вы реализуете это в рамках концептов и кто в этот раз придёт на зов рунических записей?
2) Из наличия оператора ++ не следует принадлежность к классу итераторов. Из наличия operator bool() не следует валидность при истинном значении. Бинарные операции не обязаны быть рефлексивными и транзитивными, да и геометрия не вписывалась всегда быть евклидовой. Поэтому ваш подход "если это выглядит и пахнет как банан, то это банан" заранее ущербен.
Мне не придётся ничего дописывать, так как у указателя, встроенного типа, уже есть операторы ++ и --, да и оператор * в наличии.
Так что operator== для него будет ровно однозначен, как и isPointer.
а теперь почитайте про SSO
Очень интересно, как вы реализуете это в рамках концептов и кто в этот раз придёт на зов рунических записей?
Здравый смысл. Если метод не определен вручную, то не придется его удалять. Помимо этого:
1. random access iterator должен обладать методом operator[].
2. ничто не мешает явно запретить методы для класса.
3. ничто не мешает поставить! перед условием концепта
4. операторы * / -> и прочие могут возвращать не указатель/ссылку на объект, а прокси-объекты. Что будете делать в таком случае?
Из наличия оператора ++ не следует принадлежность к классу итераторов
правильно. Для принадлежности к классу итераторов, оператор ++ должен не просто быть, а еще и возвращать итератор. Также должен присутствовать оператор *, возвращающий ссылку на объект. А чтобы нашего голубя за наличие перьев и клюва не приняли за утку, можно точно так же явно запретить ему крякать, UCS: MyVector {… sort() = delete;… };
SSOЕщё раз, причём тут оно, если у меня даже шаблоны не раскрыты? Причём тут SSO, если у меня ещё std::basic_string в std::string не раскрыт?
1. random access iterator должен обладать методом operator[].
2. ничто не мешает явно запретить методы для класса.
3. ничто не мешает поставить! перед условием концепта
4. операторы * / -> и прочие могут возвращать не указатель/ссылку на объект, а прокси-объекты. Что будете делать в таком случае?
1. Вот ему этот оператор и отдадим.
2. Да, только вам нужно это делать вручную или наследоваться, наращивая дерево потомков, хотя можно определить метатип, где всё уже сделано.
3. Ставьте.
4. Я — ничего, потому как в таком случае это будет не указатель. А в общем случае — буду писаать решение. Возможно — используя шаблоны.
правильно.#comment_10333414 второй спойлер.
Описанный вами подход: всё, что имеет операторы * и ++ нужной сигнатуры — итераторы. Описанный мной: всё, что подходит под критерии итераторов, может быть объявлено итераторами.
Если на итератерах разница не так очевидна, возьмите задачу сериализации. Мы можем объявить часть данных сериализуемыми и выполнять сохранение только этих данных. При этом, нам не придётся каждый раз беспокоиться, совпадут ли типы и везде ли выполнены изменения, нам даже метод (де-)сериализации объявлять не придётся. Для трейтов мы такого сделать не можем, нам в любом случае придётся обёртывать поля в некие структуры-контейнеры. Что обязательно скажется на семантике.
Причём тут SSO, если у меня ещё std::basic_string в std::string не раскрыт?
При том, что поведение всяких == для строк зависит от того, является ли строка emlaced, (это доп. проверка). При этом capacity строк может быть различной — от этого они менее равны не станут. И да: как вы явно дадите компилятору понять, что указатель на начало может быть nullptr и разыменовывать его не надо?
2. Да, только вам нужно это делать вручную или наследоваться, наращивая дерево потомков, хотя можно определить метатип, где всё уже сделано.
а как только надо сделать что-то чуть-чуть отличающееся, надо писать новый метатип, а потом на основе него тип? How about no
#comment_10333414 второй спойлер.
В вашем списке отсутствуют реализации методов begin() и end(). Оператор * итератора вашего списка вернет ноду, а не value списка. BackwardIterator является подмножеством ForwardIterator'а. Поверх этого, написать список вручную даже проще. А вдруг я хочу итератор в многомерном массиве?
Кстати, я уже говорил, что Ranges поддерживают диапазоны с разными типами begin() и end()?
возьмите задачу сериализации.
где вы сериализовали размер вектора? А где вы его десериализовали? Упс.
Я лишь пытаюсь сказать, что подгонка под ваши метаклассы в общем случае займет не меньше времени, чем написание всего класса с нуля. У вас возникает путаница с решениями, которые вы защищаете
При том, что <...>. И да: как вы явно дадите компилятору понять, что указатель на начало может быть nullptr и разыменовывать его не надо?Вы что, решили что я додумаюсь выводить operator== у std::string через метаклассы? Метаклассы не для этого. Вообще, зачем мне переписывать std::string, если у него уже определено всё интересующее меня поведение?
а как только надо сделать что-то чуть-чуть отличающееся, надо писать новый метатип, а потом на основе него тип? How about noА сейчас с шаблонами разве не так?
В вашем списке отсутствуют реализации методов begin() и end().Простите, что не написал реализацию всего std.core через метатипы. Что за детский сад?
где вы сериализовали размер вектора? А где вы его десериализовали? Упс.Очень интересно, как вы сериализуете динамический массив на сырых указателях? (Ответ: никак, хотя и можно потрахаться со смещениями и сырым считыванием памяти.) А с контейнерами у меня всё будет просто:
template <class T>
$class container {
using Type = T;
size_t size() const;
iterator begin() const;
iterator end() const;
void append(const Type & t);
};
/* Грубо, ибо синтаксиса ещё нет, да и мне влом снова писать сериализацию типов*/
$class serializable {
constexpr {
for... (auto v : serializable$.values()) {
/* ... */
if (v.is(container) && v$.Type.is(serializable))
-> {
v.name()$.size().serialize();
for (auto i = v.name()$.begin(); i != v.name()$.end(); ++i)
(*i).serialize();
}
/* ... */
auto size_type = serializable$.method("size").return_type().name();
-> {
size_type$ sz = 0;
sz.unserialize(buffer_reader);
for (size_type$ i = 0; i != sz; ++i) {
Type$ t;
t.unserialize();
v.name$.append(t);
} } } }
};
И, заметьте, в кой-то веке запись мета-уровня выглядит единообразно с остальной программой. Никаких проваливающихся на дно #if !defined(QT_NO_UNSHARABLE_CONTAINERS), никаких уходящих в небо проверок на std::is_array::value, вся запись выглядит как один и тот же язык программирования.
И, заметьте, в кой-то веке запись мета-уровня выглядит единообразно с остальной программой
посчитайте число спец. символов в вашем коде и в коде через шаблон.
Никаких проваливающихся на дно #if !defined(QT_NO_UNSHARABLE_CONTAINERS), никаких уходящих в небо проверок на std::is_array::value, вся запись выглядит как один и тот же язык программирования.
Где вы последний раз видели «проваливающиеся на дно» дефайны, кроме как для определения фич платформы/компилятора?
С концептами все std::is_array::value не нужны. Вы о них вообще читали? Они реализованы в gcc аж с шестой версии, сейчас head — восьмая
std::array тоже контейнер, но при его сериализации не нужно записывать размер, т.к. он содержится в типе. И снова ваш serializeable не подойдет.Сделайте это всё на шаблонах, потом поговорим.
посчитайте число спец. символов в вашем коде и в коде через шаблон.Сначала расскажите, как в чисто процедурном языке появился функциональный метаязык и приведите аргументы, почему я в процедурном языке должен отказаться от процедурного метаязыка, оставаясь на функциональных шаблонах, от которых даже ярых хаскельщиков мутит. А уже потом мы с вами сигнумометрией позанимаемся.
Где вы последний раз видели «проваливающиеся на дно» дефайны, кроме как для определения фич платформы/компилятора?
Ну, чего далеко ходить? Концепты для отчаявшихся
Концепты, напомню, ещё не включены в стандарт, как и модули. И то, что они работают в gcc аж с шестой версии совершенно не гарантирует их работу в VC и Clang. И, тем более, не гарантирует их одинаковое ожидаемое поведение.
Сделайте это всё на шаблонах, потом поговорим.
Qt же сделали как-то сериализацию всех своих классов, на шаблонах. Даже без с++11.
Сначала расскажите, ...
то, что есть сейчас, не идеально — не спорю. Многие решения в с++ были приняты, скажем, неаккуратно. Но неужели это значит, что надо заменять недоделанное недоделанным? Беглый осмотр propolsal'а по метаклассам и я нахожу там около 4-5 изъянов.
Ну, чего далеко ходить? Концепты для отчаявшихся
С синтаксисом rvalue факап вышел, имо. Хотя, проблема в статье основана на том, что автор не знал про std::forward
И то, что они работают в gcc аж с шестой версии совершенно не гарантирует их работу в VC и Clang
корректную работу в VC вообще никто и ничто никогда не гарантирует, включая стандарт. Это касается не только концептов, ренджей и модулей (которые именно ms и продвигают), но и вот этих вот метаклассов, ведь interface — макрос VC для struct
Qt же сделали как-то сериализацию всех своих классов, на шаблонах. Даже без с++11.Автоматический вывод — никак. И не на шаблонах, на шаблонах только контейнеры — и то через жопу. Достаточно сказать, что простое объявление
enum class myEnum: quint8{ ZERO = 0; };
myEnum my = myEnum::ZERO;
QDataStream data;
/* init QDataStream */
data << my;
вываливает ошибку компиляции. И нужно городить шаблон каста в базовый тип или определение оператора. Пример не засчитан.Всё же, как будет выглядеть шаблон, который добавляет произвольному классу сериализацию? Ну?
то, что есть сейчас, не идеально — не спорю. Многие решения в с++ были приняты, скажем, неаккуратно. Но неужели это значит, что надо заменять недоделанное недоделанным? Беглый осмотр propolsal'а по метаклассам и я нахожу там около 4-5 изъянов.Повторяю вопрос: почему я в процедурном языке программирования не имею права на долбаный процедурный метаязык? Никто же не призывает заменить шаблоны полностью, уж слишком прочно шаблоны вошли в нашу жизнь, да и назначение у них разное. Но можно предоставить программистам единообразный инструмент?
корректную работу в VC вообще никто и ничто никогда не гарантирует, включая стандарт.Ну да, cl бастует, если функция не возвращает значение int foo(){}, хотя формально это UB. Отошли от стандарта, вай-вай! Сколько раз уже бывало, когда плюшки из экспериментального gcc после принятия нового стандарта приобретали совершенно обратный вид, так что код сливался в канализацию? Да что там gcc, у нас комитет с initialization_list до сих пор решиться не может, как он должен работать. Каждый пишет кто во что горазд, ну а виноват, естественно, мелкомяхк.
вываливает ошибку компиляции
enum class в общем и целом придуман сугубо для того, чтобы запретить неявный каст его значений в целочисленные типы.
Всё же, как будет выглядеть шаблон, который добавляет произвольному классу сериализацию? Ну?
Как будет выглядеть метакласс, который добавляет произвольному классу сериализацию? Никак, ведь все варианты не учтешь. То же самое и с шаблоном.
Повторяю вопрос: почему я в процедурном языке программирования не имею права на долбаный процедурный метаязык?
Я написал это раза три и повторю в четвертый: автоматическая генерация кода полезна лишь в очень узком диапазоне случаев, которые еще и тривиальны в реализации. Как только дело доходит до чего-то нетривиального, часть автогенерируемых методов не подходят.
Отошли от стандарта, вай-вай!
ознакомьтесь. Для справки: gcc и clang уже несколько месяцев имеют 100%-ую поддержку с++17
enum class в общем и целом придуман сугубо для того, чтобы запретить неявный каст его значений в целочисленные типы.
template <class Type>
QDataStream & operator<< (QDataStream & stream, const Type & t)
{
return (stream << static_cast<typename std::underlying_type<Type>::type>(t));
}
template <class Type>
QDataStream & operator>>(QDataStream & stream, Type & t)
{
typename std::underlying_type<Type>::type temp;
stream >> temp;
t = static_cast<Type>(t);
return stream;
}
Пример.
В Qt просто нет шаблонов для сериализации. Ваш пример идёт поперде. Есть другой?
Как будет выглядеть метакласс, который добавляет произвольному классу сериализацию? Никак, ведь все варианты не учтешь. То же самое и с шаблоном.Отговорка. Нам не нужно учитывать все варианты. Нам нужно отработать очевидные и сообщить о всех тех, которые не смогли разрешить.
Как только дело доходит до чего-то нетривиального, часть автогенерируемых методов не подходят.Иначе бы наша профессия не была востребована.
Вот пример: попробуйте написать метакласс-сериализатор для классов: array, list, vector, tuple, string, pair, shared_ptr, optional и variant. С одним условием: нельзя использовать std::is_same и концепт Same. Надеюсь, где-то на 5% до вас дойдет что же я имел в виду.
Мне интересно взглянуть на гения чистой красоты, который додумался сериализовать tuple, да и array тоже. Вас в них ничего не смущает? Нет?
Подсказка: анонимная, этапа компиляции, фиксированная. Нет? Не звонит колокольчик?
Но даже если такие господа есть, кто наложил такое ограничение?
Подсказка: анонимная, этапа компиляции, фиксированная. Нет? Не звонит колокольчик?
еще раз. Вы сами сказали, что понадобится ОДИН метакласс serializable.
$Container MyContainer {
$RandomAccessIterator iterator {};
};
// определение методов MyContainer begin(), end(), size(), ...
// А также методов MyContainer::iterator ==, +(int), ++, --, -(int), *,...
Таким образом, мы имеем минималистичный интерфейс контейнера, а компилятор сразу ругнется на отсутствие реализации необходимого метода.
Нужен также стандартный способ получения имени класса/имен значений enum'ов, оборачивания свойств — того, что сейчас реализуется только макросами/препроцессором.
Что до определения методов — не стоит даже пытаться, слишком много условностей. Это я в общем-то и пытался доказать iCpu. Даже в примере Саттера с точкой зачем-то добавлены операторы сравнения (лексикографического), хотя логики в этом нет.
п.с. а как сделать комбинированный метакласс? Или там этого не было?
Что до определения методов — не стоит даже пытаться, слишком много условностей.Как будто голос из глубин 90-х! «Что до параметризации алгоритмов — не стоит даже пытаться, слишком много условностей. Даже в примере Страуструпа с quick_sort зачем-то добавлены макросы, хотя логики в этом нет.»
Antervis, вы привязываетесь к мелочам, не пытаясь увидеть главного. За деревьями леса не видите, как говорится. Вам дают синтаксис для определения поведения по умолчанию. Если сейчас вам дают написать class MyClass { MyClass() = default; };, то предполагается дать вам право определять это самое default. Именно в этом заключается предложение. Будет его синтаксис более процедурный или шаблонный — это уже четвёртый вопрос. А вы носитесь, как маленький мальчик «А у него там точка стоит!»
а как сделать комбинированный метакласс? Или там этого не было?Было. Пункт 2.5. Предлагается вводить примерно так же, как сейчас со множественным наследованием. Слева направо, по очереди. Если есть конфликт — вываливаться на попытке использовать код с конфликтом или на попытке каста в каждый из базовых типов при компиляции. ($D:A,B,C{}; class d :$D{}; assert(d.is(A) && d.is(B) && d.is( C), «Metaclass composition conflict.») Учитывая, что на множественном наследовании это работает, нет причин паниковать.
еще раз. Вы сами сказали, что понадобится ОДИН метакласс serializable.Промотал назад, подумал, что меня переглючило. Нет, не меня.
Метапрограммирование занимаете уходом от частного к общему.Лол, нет. Метопрограммирование занимается не индукцией, а генерацией. Не разработкой, а штамповкой. Есть разница между добавлением методов сериализации к кортежу и к 100500 структурам из тривиальных типов.
0xd34df00d, tuple, array и optional параметризуются своими типами на этапе раскрытия шаблонов. На выходе мы будем иметь структуру с методами доступа по индексу элемента, статический массив на стеке и структуру с флагом (в статической или динамической памяти). Если optional удобен, а array допустим, то идея хранения в tuple данных под сериализацию не особо ясна. Но кто я такой, чтобы судить? Важно то, что мы получим те же структуры, только через постель. Так в чём проблема? Только в интерпретации и привычках.
На выходе чего именно? Метакласса? Какая разница, если сериализовать/десериализовать он будет корректно, а о типобезопасности позаботится тайпчекер?Пёс знает, у нас ещё нет даже программы обещаний, только общее «а ведь неплохо было бы», а мы уже шкуру делим. Глупо это.
Что именно вы считаете неясным?
анонимность туплов и, как следствие, эквивалентность тупла (Double, Double, Double), представляющего RGB-цвет и тупла (Double, Double, Double), представляющего координату в трехмерном пространстве.Сами же ответили. Туплы имеют смысл для сохранения временных данных в некотором куске алгоритма. Ну, например, промежуточные расчёты перехода с декартовых координат проекта в эллиптические спутников GPS\Глонасс. Они используются два раза: в первый и в последний. А когда мы храним те же цвета, последнее дело делать их анонимными. Должны быть именованные структуры. Если мы, конечно, не желаем быть проклятыми нашими последователями.
ПС
Уже много лет грызет меня эта мысль, что вот так вжух и в один прекрасный момент не смогу найти годных сотрудников крестовиков. Прям фобия.
А наследование метаклассы будут поддерживать?
Метаклассы в C++