LINUX.ORG.RU

То что сложно реализовать на других языках

 


0

3

Разбираюсь с библиотекой qsp и решил с вами поделится кусочком кода, а точнее реализация crc32. Автор кода, решил создать свой собственный crc32, на базе CRC-32/ISO-HDLC, взяв от туда таблицу, но изменил стартовое значение, и еще по мелочи:

int qspCRC(void *data, int len)
{
    unsigned char *ptr;
    int crc = 0;
    ptr = (unsigned char *)data;
    while (len--)
        crc = (qspCRCTable[(crc & 0xFF) ^ *ptr++] ^ crc >> 8) ^ 0xD202EF8D;
    return crc;
}

В чем прелесть: Использует int, но фактически работает как unsigned int. Вся логика на переполнение - и это как то работает, хотя вроде это UB. Автор кода крут (без шуток), я не спорю, но переписать это на любой другой язык, который будет контролировать переполнение - будет крайне сложно. Почему автор не использовал любой из стандартных алгоритмов crc, как он использовал библиотеку для regexp - загадка.

★★★★
Ответ на: комментарий от alysnix

К чему все эти макросы? Вот так просто выражаются эти твои трейты:

template <typename T>
class Readable {
  virtual bool can_read();
  virtual T read();
};

class SomeClass {};

class ImplReadableSomeClass : public Readable<int> {
  ImplReadableSomeClass(SomeClass &super);
  bool can_read() override;
  int read() override;
};

typename <T>
void read(Readable<T> &obj);
Используя этот подход, потеряется ровно 0 преимуществ трейтов.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

не. это не выражается через виртуальные функции, за счет утиной типизации там функции обычные.

применением виртуальности ты ухудшаешь свойства класса. он будет с виртуальной таблицей и деструктором. это ломает всю идею.

а «трейт в с++» (в виде макры) обязует тебя просто реализовать перечисленные декларации. а совместимость по трейту будет утиной(которой в с++ нет).

alysnix ★★★
()
Ответ на: комментарий от alysnix

не. это не выражается через виртуальные функции

Так еще долго можно продолжать, покажи конкретный пример на Rust который не выражается таким образом.

применением виртуальности ты ухудшаешь свойства класса

У SomeClass не будет виртуальной таблицы, у ImplReadableSomeClass в общем то тоже, будет произведена девиртуализация. А там где она будет, там у Rust будет dyn Trait, это тоже самое.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

Используя этот подход, потеряется ровно 0 преимуществ трейтов.

трейты как «требования», можно использовать везде где такие требования встают. например для ограничений свойств параметров в списках шаблонов. ту ж не будешь там требовать реализации неких виртуальных функций в виде интерфейсов. это слишком сузит применимость

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

например для ограничений свойств параметров в списках шаблонов.

Мы все еще о Rust говорим? Там невозможно ограничить генерик трейтами, там можно только передать трейт-абстрактный класс, выше примеры с нерабочим add в Rust, и рабочим add в С++. Короче жду конкретный пример из Rust.

Если выше непонятно написал, то в Rust:

fn f<T: Trait>(x: T) {}
Равно коду из С++
void f(Trait &x) {}
То что у Rust синтаксис выглядит не как передача абстрактного класса, а как генерик, сути не меняет нисколько, это лишь синтаксис.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 4)
Ответ на: комментарий от MOPKOBKA

у Long и Integer есть оператор плюса. Можно было бы их передать, но у Java стирается тип в генерики, и она не сможет принять такую функцию все равно.

Я тебе ещё раз говорю, что стирание типов тут не при чём. Для произвольного неизвестного типа T не определён оператор (+). В C++ такое работает, потому что шаблоны инстанциируются во время применения, и делают это почти простой текстовой подстановкой, то есть генерируют исходный код, который уже потом компилируется, они не проверяют, что у подставляемого типа есть нужный оператор ДО подстановки. В отличие от Java, Rust, C# и многих других.

В C# нет стирания типов, но это тоже не будет работать:

	public static void Main()
	{
		Console.WriteLine("{0}", Add(1, 2));
	}
 
	static <T> T Add(T a, T b) {
		return a + b;
	}
-- ошибка компиляции.

	public static void main (String[] args) throws java.lang.Exception
	{
		System.out.println(add(1L, 2L));
	}
 
	static <T extends Long> Long add(T a, T b) { // <- вот так будет, но только с приведением к Long и потерей типа T на выходе
		return a + b;
	}

В Хаскелле не будет работать, нужно, чтобы для типа был реализован тайпкласс Num. Нигде (с оговорками) не будет работать, кроме C++.

korvin_ ★★★★★
()
Последнее исправление: korvin_ (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

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

короче копай идею - трейты это просто декларации, к виртуальности они не относятся(хотя она может там и возникнуть), типизация утиная.

alysnix ★★★
()
Ответ на: комментарий от MOPKOBKA

Похоже, что ты вцепился зубами за вторичный признак - за само слово «trait». Знал одного очень уважаемого эрлангиста, который считал, что trait в rust - это якобы очень близко к тому, что обозначается словом trait в той же scala. Смешно, конечно!

Кстати, как эрлангист, он действительно хорош. Прошло уже лет 6-7, и он до сих успешно пишет на эрланге. А вот разобраться в том, что же такое trait, он не смог. Бывает.

Вообще, говоря о параллели между трейтами в rust и классами типов в haskell, мне нравится следующее сопоставление.

Вот, в расте есть такое понятие как «trait object». Простая и понятная штука. В хаскеле это тоже есть, но искать надо в разделе «existential quantification».

Зачем так сделали? Понятия не имею. Хотя для практики очень полезный сценарий использования.

Возможно, что многие в ступор входят, когда что-то сравнивают с хаскелем. А так, еще, еще и еще раз повторяю, что трейты в расте очень близки по сути классам типов из хаскеля. И рассматривать это надо в контексте написания ограничений для обобщенного кода.

Об этом все пишут, кто хоть сколь-нибудь знаком с обоими языками. Не понимаю, зачем пытаться опровергать очевидное любому образованному программисту? Чтобы что?

anonymous
()
Ответ на: комментарий от korvin_

В C# нет стирания типов

Я не говорю что в Rust есть буквальное стирание типов из Java, в Rust то и Object нету. Я лишь про то, что изначальный тип теряется как только вызывается функция, и больше недоступен в отличии от С++.

Я тебе ещё раз говорю, что стирание типов тут не при чём.

Конечно причем, потому что невозможно вызывать внутренний add_int_int, потому что невозможно проверить тип в момент a + b.

В C++ такое работает, потому что шаблоны инстанциируются во время применения, и делают это почти простой текстовой подстановкой

И эта подстановка как ты ее назвал, это межзвездный космический корабль, по сравнению со стиранием типа (палкой копалкой) в других языках.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от anonymous

Между trait в Rust и PHP есть лишь одно отличие, в Rust trait можно реализовать для типа после его объявления.

Эти стены текста бесполезны, как говорил Линус, заткнись и покажи код. Так вот, я выше просил показать мне пример с трейтами на Rust который не реализуется в С++ через абстрактные классы без потери каких нибудь важных свойств: То что сложно реализовать на других языках (комментарий)

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от korvin_

Я тебе ещё раз говорю, что стирание типов тут не при чём.

Причём, тебе верно сказали.

В C++ такое работает, потому что

Потому что тип не стирается.

В C# нет стирания типов

Есть. Любой генерик мономорфен и стирает тип. Работает он только с одним типом(подтипом/трейтом/базовым классом). Не стирают только шаблоны, где код полиморфен.

– ошибка компиляции.

Потому как типы затёрты и язык не знает, что у T есть op+.

static Long add(T a, T b) { // <- вот так будет, но только с приведением к Long и потерей типа T на выходе

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

anonymous
()
Ответ на: комментарий от MOPKOBKA

Конечно причем, потому что невозможно вызывать внутренний add, потому что невозможно проверить тип в момент a + b.

:facepalm: Это не имеет вообще никакого отношения к стиранию типов. Что значит «невозможно проверить тип»?

И эта подстановка как ты ее назвал, это межзвездный космический корабль

Ага, а препроцессор Си так вообще Звезда Смерти. А Концепты и констрейнты в C++ добавили, видимо, чтобы этот космический корабль приблизить к палке, да?

по сравнению со стиранием типа (палкой копалкой) в других языках.

Ты, похоже, понятия не имеешь, о чём говоришь.

Я не говорю что в Rust есть буквальное стирание типов из Java, в Rust то и Object нету.

А что ты говоришь? Нет «стирания типов из Java», есть «стирание типов» и всё. Оно либо есть в языке, либо нет. Либо, может быть, есть местами.

korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

Ага, а препроцессор Си так вообще Звезда Смерти. А Концепты и констрейнты в C++ добавили, видимо, чтобы этот космический корабль приблизить к палке, да?

И без концептов был enable_if, мой пример выше в них не нуждается, они просто красивее.

Ты лучше подумай, почему С++ предоставляет больше возможностей, и почему другие языки не могут реализовать подобную систему. Потому что это трудно, это более совершенная и более сложная система. Препроцессор в Rust например есть, это его макросы, только вместо понятного синтаксиса /usr/bin/cpp был взят видимо m4.

темплейты лучше

Ты, похоже, понятия не имеешь, о чём говоришь.

Почему пример выше реализуем на С++, но не реализуем на более прекрасном Rust или Haskell? Почему С++ позволяет делать больше?

Почему твои функции забывают типы которые они принимают, и не могут вернуть оригинал? Потому что вместо генериков или шаблонов, они реализуют в том или ином виде абстрактные классы, их тоже нельзя привести в нормальный вид после передачи.

В С++ есть и абстрактные классы, и нормальные шаблоны. То есть он более продвинут.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 7)
Ответ на: комментарий от korvin_

Нет. Ты тоже, видимо, не понимаешь что-такое type erasure.

Есть. Ты не понимаешь, что такое тип в принципе. Чем отвечать выборочно и игнорировать неудобное, ты лучше мне объясни, каким образом здесь:

static <T extends Long> Long add(T a, T b) { // <- вот так будет, но только с приведением к Long и потерей типа T на выходе
		return a + b;
	}

у тебя теряется тип возврата? Язык не смог вывести тип a + b, где типы a и b известны и не затёрты(по твоему утверждению)?

anonymous
()
Ответ на: комментарий от MOPKOBKA

Ты лучше подумай, почему С++ предоставляет больше возможностей

Потому что в него их напихивают.

и почему другие языки не могут реализовать подобную систему.

Ты лучше подумай «не могут» или «не хотят».

Почему пример выше реализуем на С++, но не реализуем на более прекрасном Rust или Haskell? Почему С++ позволяет делать больше?

Чего больше?

Почему твои функции забывают типы которые они принимают, и не могут вернуть оригинал?

Что значит «забывают типы»? Они их _не знают_. Если ты любишь думать, то подумай, почему в C++ шаблоны нужно было размещать в хедерах, а в других языках — нет. Там, где нет хедеров, можешь взять одельно файл с интерфейсом, отдельно — с классом-реализацией.

Потому что вместо генериков или шаблонов, они реализуют в том или ином виде абстрактные классы

Понятно, у тебя ещё и своё какое-то определение генериков, вместо общепринятого. :facepalm:

В С++ есть и абстрактные классы, и нормальные шаблоны. То есть он более продвинут.

А в Common Lisp можно любой код выполнять во время компиляции, а не только constexpr, значит, он ещё более продвинут? Или что? Я так понимаю, по улице ты перемещаешься на космическом корабле?

korvin_ ★★★★★
()
Ответ на: комментарий от anonymous

Есть.

Расскажи каким образом.

Ты не понимаешь, что такое тип в принципе.

Смешно. Спасибо.

Чем отвечать выборочно и игнорировать неудобное

Отвечать на что? На чушь «Любой генерик мономорфен и стирает тип.». Сходи прочитай, что такое стирание типов, почему оно есть в Java, и почему его нет в C#, тогда будет на что отвечать.

каким образом здесь:
у тебя теряется тип возврата? Язык не смог вывести тип a + b, где типы a и b известны и не затёрты(по твоему утверждению)?

Во-первых, я же писал, что в Java дженерики реализованы через стирание типов. Во-вторых, компилятор и вывел тип a + b как long, поскольку ему известно, что 1) тип T является наследником Long и может быть приведён к нему, а 2) оператор + определён для long. Всё. Это НЕ пример отсутствия стирания типов. Это даже не пример стирания типов, хотя оно тут есть.

korvin_ ★★★★★
()
Последнее исправление: korvin_ (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

Чтобы что? Опровергнуть написанную тобой глупость? Ага, счас, разбежался. Я помню, как ты нес полнейшую дичь по односвязным спискам (прикалывался? ведь ты не в серьез?).

Есть книги. Есть много упоминаний даже в этом треде. Вон, hateyoufeel послушай - он где-то тут рядом крутится под видом анонимуса (мой старый ник он тоже должен помнить, но не скажу). Море информации.

Ладно. Начинает надоедать. Одно и тоже, из пустого в порожнее - и с нулевым результатом.

Хотя те, кто будет читать этот тред, возможно, что задумаются. Может быть, новые растоманы или новые хаскелисты появятся. Если на двадцать новых плюсовщиков появится хотя бы один хаскелист или растоман, то это уже будет отличным результатом

anonymous
()
Ответ на: комментарий от korvin_

Ты лучше подумай «не могут» или «не хотят».

Зависит от языка, некоторым нравятся простые концепции, к Rust это явно не относится.

Что значит «забывают типы»? Они их _не знают_.

Одно и тоже, главное что не видят их внутри функции, поэтому не могут вызвать Long::+(), а вот С++ может.

А в Common Lisp можно любой код выполнять во время компиляции, а не только constexpr, значит, он ещё более продвинут?

Никакой ценности в выполнение кода во время компиляции нету, это реализуется очень-очень просто и есть в любом Forth. Только там нету соблюдения семантики таргета при кросскомпиляции например, а в С++ есть.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от anonymous

Чтобы что?

Действительно, зачем заведомо провально пытаться показать что в Rust есть что то кроме урезанных абстрактных классов, когда можно рассуждать о каких то эрлангистах.

В очередной раз доказал что растофанаты не понимают сами, что такое их трейты.

MOPKOBKA ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Одно и тоже, главное что не видят их внутри функции, поэтому не могут вызвать Long::+(), а вот С++ может.

Так я тебе объяснил, почему C++ может. Потому что это кодогенерация, практически как с макросами C. Со всеми вытекающими плюсами и минусами.

Никакой ценности в выполнение кода во время компиляции нету,

Поэтому в C++ добавили constexpr? KEKW.

Тогда так: никакой ценности в шаблонах нету.

это реализуется очень-очень просто и есть в любом Forth.

Но нету в C++. Почему? Выходит, C++ не такой крутой, и позволяет меньше, чем Forth.

korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

Так я тебе объяснил, почему C++ может. Потому что это кодогенерация, практически как с макросами C. Со всеми вытекающими плюсами и минусами.

Где у макросов enable_if? Где у макросов рекурсивное выполнение? Где у макросов возможность взять разнородный список аргументов и произвести по нему foreach? Где у макросов возможность получить поля структуры по темплейту и сгенерировать на их основе код? Где у макросов возможность назначить тип выхода на основе действий в теле? Очень странные у тебя макросы в С.

Никакой ценности в выполнение кода во время компиляции нету

Поэтому в C++ добавили constexpr? KEKW.

Нету никакой ценности если она просто есть, вот например constexpr позволяет передать результаты еще и в саму программу. Или например соблюдать семантику таргета, вот это ценно, и это то чего нету у CL. А Common Lisp позволяет ... генерировать код? Вот бы такой механизм был в С++, да?

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от korvin_

Расскажи каким образом.

Я рассказал выше. Мономорфный код не может работать с более чем одним типом. Генерики мономорфны. Ты меньше игнорируй, что тебе пишут - тогда и вопросов не будет.

Смешно. Спасисбо.

Смешно. Спасисбо.

Сходи прочитай, что такое стирание типов, почему оно есть в Java, и почему его нет в C#, тогда будет на что отвечать.

Зачем мне что-то читать, если я в отличие от тебя, знаю что это такое. И уж тем более в контексте типов ссылаться на скриптуху - это прям сильно.

компилятор и вывел тип a + b как long

Потому как ничего кроме «T является подтипом long» ему не известно, какие типы передали снаружи - не известно. Иначе он бы вывел не long, а decltype(a + b) и никакой потери там не случилось бы. Как этого не случается в цпп.

auto add(auto a, auto b) { return a + b; }
static_assert(__is_same(decltype(add(0, 0)), decltype(0 + 0)));
static_assert(__is_same(decltype(add(0, 0l)), decltype(0 + 0l)));
static_assert(__is_same(decltype(add(0., 0)), decltype(0. + 0)));

Нигде тип не потерялся. Это и есть статический полиморфизм. А вот генерики мономорфны, поэтому там на любой тип агрументов будет один и тот же возврат. Потому как разные возвраты невозможны в случае стёртых до одинакового подтипа аргументов.

тип T является наследником Long и может быть приведён к нему

Ну да, сам переданный снаружи тип не известен и стёрт до подтипа «long». О чём тебе и сказали.

anonymous
()
Ответ на: комментарий от korvin_

Ты лучше подумай «не могут» или «не хотят».

Не могут. Для рассказов про «не хотят» нужны свидетельства того, что они могут. Эти свидетельства есть? - нет. Поэтому «не хотят» - просто оправдание.

Ты бы тоже мог понимать тему и разбираться в языках. Просто ты не захотел. И каждый бомж мог бы быть миллионером. Просто настроения не было. Или раст мог бы иметь свой компилятор/модель памяти/модель исполнения. Просто лень было.

anonymous
()
Ответ на: комментарий от anonymous

погоди, ты же выше рассказывал «концепты подсмотрены в тайпклассах из хаскеля», а теперь уже оказывается в цпп аналога нет? Как так вышло?

ну скорей трейты раста подсмотрены там. потому что уж очень похоже. плюсовые концепты я смотрел через пень-колоду, просто потому, что они мало жить помогают, лишь накладывают ограничения на параметры в темплейтах. для тех, кто пишет библиотеки темплейтов это мастхэв, а для тех, кто пользуется темплейтами для себя - напоминает без ручки чемодан.

короче в своих темплейтах и итак сразу вижу, где что-то не то.

вообще очень сложно говорить с анонимусами, когда их несколько. и не поймешь, кто их них кто, и потому можно запулить ошибочный ответ не тому.

alysnix ★★★
()
Ответ на: комментарий от alysnix

ну скорей трейты раста подсмотрены там. потому что уж очень похоже.

Ну да, которые базовые/абстрактные классы из цпп. Там конечно есть ещё сахарок, который в цпп отсутствует. Но в цпп он и не нужен. Шаблоны мощнее во всём.

плюсовые концепты я смотрел через пень-колоду, просто потому, что они мало жить помогают, лишь накладывают ограничения на параметры в темплейтах.

Ну в си с классами(и вообще вне шаблонного кода) концепты неприменимы. Для понимания нужно писать что-то цпп’шное.

В общем, до тех пор, пока ты не столкнёшься с проблемой, решаемой каким-то средством, ты и не поймёшь, зачем это средство нужно. С рефлексией у тебя то же непонимание, насколько помню из прошлых тем.

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

Ну да, в простейших шаблонах(aka wraped_value { auto value; }) тебе такое не понадобится. Это нужно только когда есть более одной перегрузки/специализации.

anonymous
()
Ответ на: комментарий от MOPKOBKA

Tо что мы определили трейт для i32, не значит что остальные типы для которых определён Trait должны перенять свойства i32, с чего вдруг? Поэтому компилятор и зарубает, прилететь может что угодно что удовлетворяет Trait но не умеет в +, т.е. надо клиенту кода дополнительно указать что можно подставлять:

pub fn add<T>(a: T, b: T) -> T
where T: Trait + Add<Output=T>
{
    return a + b;
}

В общем, и там и там параметрический полиморфизм, отличаются только РЕАЛИЗАЦИИ. Плюсы в месте определения ничего не проверяют(если без концептов), а просто бросают типы в стену в месте вызова, что прилипло то и ладно(та самая шаблонная утиная типизация), с очень увлекательной отладкой если ни что не подошло

zurg
()
Последнее исправление: zurg (всего исправлений: 1)
Ответ на: комментарий от anonymous

Совместим, ты не разбираешься в теме. Пара минорных отличий несовместимостью не является.

Берём код на Си. Пытаемся скомпилировать компилятором для С++. Упс! Ошибка!

Тебе нужно показать симула-ооп в цпп, а не кто там что-то сказал.

Создатель языка C++ утверждает, что в C++ модель ООП взята из Симулы. Анон на ЛОРе утверждает, что это не так. Кому же мы верим?

Я сказал, когда в цпп появились концепты. Нет, не 20. Где-то в районе 90.

Ну так ты п%#$ишь в наглую. Этим и объясняется остальной бред, что ты написал.

anonymous
()
Ответ на: комментарий от MOPKOBKA

Нет не равно, ‘‘‘void f(Trait &x) {}’’’ - здесь Trait тип, возможно полиморфный через подтипирование(абстрактное наследование), но не параметрический. В расте подтипирование есть только для лайфтаймов

zurg
()
Ответ на: комментарий от MOPKOBKA

насколько я понимаю основную идею тех кто это в расте придумывал.

  1. утиная типизация.

  2. трейты, это просто список заголовков функций(в терминах си), что некто должен реализовать.

То что тип S реализует трейт T - означает, что есть обычные статические функции с первым параметром как &S, и с менглированными именами (чтобы было понятно к какому типу и трейту она относится, например -> func1_T1_S1)

сам тип S (struct в расте) - не имеет вообще указателя на vmt никогда, потому можно трейты безболезненно привязвать к любому типу, даже скаляру(с расте можно? не помню)

  1. что делать если по коду нужна динамическая диспетчеризация через трейт T? тогда указатель на обьект S передается вместе с указателем на дескриптор трейта T(«таблицу функций трейта T_для_типа_S»).

в виде:

(ссылка_на_обьект_S, таблица_функций_T_для_S),

получается аналог виртуальных методов в си. просто вызывается i-тая функция из этой таблицы, и толкается ссылка на обьект как первый параметр.

  1. Поскольку нет наследования типов, то указатель(ссылка) на некий Struct, всегда на него указывает. То есть тип прибит, и не может указывать на другой тип. Потому всегда можно от такого обьекта породить пару (ссылка, таблица трейта(раз тип известен при компиляции, то таблица трейта для него находится однозначно)). И передать эту пару в функцию, что требует ссылку на трейт(где нужна динамическая типизация)

итак, что мы получили?

Трейт не добавляет в обьект никаких указателей на дескриптор класса. из-за этого имея только указатель на обьект нельзя делать динамическую диспетчеризацию. Зато не меняется размер и тривиальность класса, и можно трейты реализовывать для скаляров.

Виртуальность в явном декларативном виде отсутствует, но делается неявно через пары (ссылка,таблица) там где она нужна.

Сама таблица(VMT) для реализации трейта T типом S есть(если виртуальность там нужна).

Короче вот так я понял этот ваш раст.

alysnix ★★★
()
Ответ на: комментарий от anonymous

Берём код на Си. Пытаемся скомпилировать компилятором для С++. Упс! Ошибка!

Нет, никакой ошибки не будет.

А так, берём код на Си. Пытаемся скомпилировать компилятором для Си. Упс! Ошибка! Погодите, Си не совместим с Си? Или Си не является Си? Нет, всё гораздо проще - мы просто послушали агитатора скриптухи с лора.

И как обычно пропагандист отвечает выборочно.

Кому же мы верим?

Поплыл? Но в целом предсказуемо было уже тогда, когда ты начал прятаться за кого-то.

Ну так ты п%#$ишь в наглую.

Ну да, ну да. Всё что противоречит вере - это просто оппонент наврал.

Нет, маня. Просто ты слишком слаб и понятия не имеешь ни о концептах, ни о цпп в целом.

anonymous
()
Ответ на: комментарий от zurg

Ну так я и говорю что это абстрактный класс, const никогда не забирает у тебя сам тип, а Rust забирает тип и дает возможность работать только с абстрактным классом. Надо тебе трейты не с шаблонами сравнивать, а именно с абстрактным классом, там и возможности те же, и проверяется все как ты хочешь.

MOPKOBKA ★★★★★
()
Ответ на: комментарий от alysnix

утиная типизация.

Да вроде нету, ничего не будет работать пока не укажешь impl X for Y

трейты, это просто список заголовков функций(в терминах си), что некто должен реализовать.

Прямо как абстрактные классы в моем примере. Если не реализуешь, то и не передашь в функцию.

Трейт не добавляет в обьект никаких указателей на дескриптор класса

В моем примере тоже ничего не добавляется, vtable не создается.

Зато не меняется размер и тривиальность класса, и можно трейты реализовывать для скаляров.

Да, прямо как в моем примере, можешь хоть для void реализовать.

MOPKOBKA ★★★★★
()
Ответ на: комментарий от MOPKOBKA

отличие абстрактного класса от трейта(если то что я написал выше - верно) в том, что абстрактный класс сразу декларирует виртуальные функции, и все их вызовы будут идти через vmt(опускаем прямой вызов, когда тип обьекта определен однозначно).

В расте все обьекты определены однозначно всегда(нет наследования). Потому любой вызов функции трейта для обьекта будет статическим. Динамический вызов будет не от обьекта, а от пары (обьект, трейт) - там вроде называется трейт-обьект.

alysnix ★★★
()
Ответ на: комментарий от MOPKOBKA

так. ладно, я пока не понял, где там твой пример. я думал ты про абстрактные классы из си. но поскольку ты полиглот, ты может еще откуда их взял. чуть позже посмотрю.

alysnix ★★★
()
Ответ на: комментарий от zurg

Семантически равно. Разве что какого-то сахарка нет.

В любом случае, показывай отличия. Пишешь пример на растотрейтах, который невыразим на цппклассах. Вот как я выше приводил пример шаблона, который через генерики не выражаются.

anonymous
()
Ответ на: комментарий от alysnix

отличие абстрактного класса от трейта(если то что я написал выше - верно) в том, что абстрактный класс сразу декларирует виртуальные функции, и все их вызовы будут идти через vmt(опускаем прямой вызов, когда тип обьекта определен однозначно).

Ну так если ты используешь АК как трейты в расте, компилятор всегда сможет их девиртуализировать.

Динамический вызов будет не от обьекта, а от пары (обьект, трейт) - там вроде называется трейт-обьект.

Не думаю что в этом обсуждении важно как именно реализован vtable, заметь что в моем примере мы тоже по сути берем такую пару, за счет того что есть класс дающий trait, и объект который он обернул собой.

MOPKOBKA ★★★★★
()
Ответ на: комментарий от MOPKOBKA

давай попроще трейт эмулировать, такой вот плюсово-растовый псевдокод

trait Printable {
  void print(self&);
}

implement Printable for int {
  void print(int& self) { print_int(self) }
}

implement Printable for float {
  void print(float& self) { print_float(self) }
}

///вызов через трейт - тут будет трейт-обьект
void test1 (Printable &val) {
  val.print(); /// будет вызов val.table[0](val.obj)
}

void test2 () {
  ///тут вызовы чисто статические
  10.print();
  (1.1).print(); ///тут неоднозачность на точке, но нам нет преград

  //тут вызов через пару.
  test1(10); /// тут будет создана пара на стеке из ссылки на 10(не надо спрашивать как), и вирт таблицы Printable для int, и передана в test1

}
alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от alysnix

Да по моему мой способ эмуляции точно такой же простой как и у тебя: https://godbolt.org/z/W9TdfWcod

А вот с -O1: https://godbolt.org/z/exY363c3Y

Можешь и код посмотреть, генерации виртуальных методов вообще нету.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от anonymous

Нет, всё гораздо проще - мы просто послушали агитатора скриптухи с лора.

Царь? Простите, я думал, ты другой анонимус.

Всё что противоречит вере - это просто оппонент наврал.

Вере во что? В то, что ты брешешь? Ну да, это потому что ты реально брешешь как сивый мерин.

anonymous
()
Ответ на: комментарий от MOPKOBKA

для начала закоменть там реализации print, для int и float и посмотри, что будет. оставь только декларации.

иначе компайлер все инлайнит, и получается неадекватный результат.

я закоментил и там кишки повылезли

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

Это нечестно будет по сравнению с Rust, у него нету возможности порубить проект на .o файлы и собрать .rs по отдельности. Как раз сейчас получается очень адекватный результат который реально можно сравнивать с тем как компилируется Rust.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

это честно по отношению к си++. или тебе придется писать header-only код и покрыть себя позором.

а в расте нельзя обьявить что реализуешь трейт, а саму реализацию спрятать? это нехорошо. но это уже из другой оперы. сама концепция это не отрицает.

alysnix ★★★
()
Ответ на: комментарий от alysnix

это честно по отношению к си++. или тебе придется писать header-only код и покрыть себя позором.

А как там хедеры в Rust поживают?

а в расте нельзя обьявить что реализуешь трейт, а саму реализацию спрятать?

С другой стороны надо заходить, вот есть у тебя функция fn f<T: Trait>(obj: T), как она будет использовать твой Trait? Откуда она знает что нужно вызвать int::print? Для динамики синтаксис другой если что, там пишется вот так fn dynf(obj: &dyn Trait).

Если не хочешь виртуализации то нельзя скрывать реализацию, и функции которые работают с трейтами. Это так что в Rust, что в С++.

сама концепция это не отрицает.

Абстрактные классы вполне отдельно можно реализовать. Это девиртуализация невозможна без LTO.

Тебе надо поскорей принять идею что трейты = абстрактные классы, тогда станут понятны их свойства и ограничения.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 9)
Ответ на: комментарий от MOPKOBKA

С другой стороны надо заходить, вот есть у тебя функция fn f<T: Trait>(obj: T), как она будет использовать твой Trait? Откуда она знает что нужно вызвать int::print?

в моей парадигме ей ж пару дают - ссылка на обьект - ссылка на таблицу. И она знает что у трейта Printable, функция print имеет индекс 0.

просто вызывает из таблицы фунцию[0], и дает ей ссылку на обьект - вот например на 10, что на стек положили.

alysnix ★★★
()
Ответ на: комментарий от MOPKOBKA

А как там хедеры в Rust поживают?

плохо поживают. я уж говорил, что без файла экспортов, это все - игрушки.

Для динамики синтаксис другой если что, там пишется вот так fn dynf(obj: &dyn Trait).

это детали. у них там еще чота наворочено. надо посмотреть чем параметр - просто трейт, отличается от «динамический трейт».

alysnix ★★★
()
Ответ на: комментарий от alysnix

Я думал мы про Trait, но ты видимо про dyn &Trait, так вот, смотри как это выглядит в С++, точно так же: https://godbolt.org/z/o3M5KM3aW

test1(PrintableInt{10});

mov     QWORD PTR [rsp], OFFSET FLAT:"vtable for PrintableInt"+16
mov     DWORD PTR [rsp+8], 10
call    "test1(Printable const&)"

В чем отличие от vtable вообще? Точно так же test1 вызовет print из этой таблицы, зная смещение метода.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 3)