LINUX.ORG.RU

Как жить без специализации impl-ов?

 


0

6
enum Edge<T> {
	Edge(Option<T>),
	Nothing
}

impl<T> fmt::Display for Edge<T> {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		write!(f, "{}", match self {
			&Edge::Edge(_)        => "E",
			&Edge::Nothing        => "-",
		})
	}
}

impl<T: fmt::Display> fmt::Display for Edge<T> {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		write!(f, "{}({})", match self {
			&Edge::Edge(_)        => "E",
			&Edge::VirtualEdge(_) => "e",
			&Edge::Nothing        => "-",
		}, match self {
			&Edge::Edge(Some(v))        => v,
			&Edge::VirtualEdge(Some(v)) => v,
			_                           => "-"
		})
	}
}

Так нельзя, потому что в расте нет специализации. А как в таких случаях надо писать?

Замысел состоит в том, что второй impl основной, а первый — fallback на тот случай, если Edge инстанцировали от чего-то, что нельзя распечатать.

★★★★★

Последнее исправление: intelfx (всего исправлений: 2)

Можно подождать совсем немного до следующего релиза и там специализация будет.

А можно сделать так:

#[derive(Debug)]
enum Edge<T> {
	Edge(Option<T>),
	Nothing
}

struct S;

fn main() {
    let a = Edge::Edge(Some(10i32));
    println!("{:?}", a);
    
    let b = Edge::Edge(Some(S));
    //println!("{:?}", b);
}

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

Специализация будет, да, но я почитал RFC и его обсуждение и у меня немного поплыли мозги.

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

P. S.: Debug — это не эквивалентный код.

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

P. S.: Debug — это не эквивалентный код.

Ну да, но хоть какой-то выход.

В этом RFC её описывают как совершенно адское rocket science с кучей недостатков и только для каких-то особых случаев

Если честно, то я не вникал во все подробности. Вот реализуют - тогда посмотрим. Так что если тебя что-то смущает - расскажи, интересно будет послушать.

и у меня закономерно возникает вопрос — а как вообще жить без неё?

Без специализации? В смысле, как сейчас? Дублированием функций (типа to_owned/to_string) обходятся, как ещё.

DarkEld3r ★★★★★
()

http://is.gd/Dyo4hh

Пример не компилировался, пришлось чуть поправить. Первый impl более общий - перед функцией добавляем «default», остальные же реализации этой функции будут использованы при соответствии типажей.

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

Это nightly. Но да, я уже понял, что ещё чуть-чуть — и заживём.

Меня скорее такая штука интересует ( DarkEld3r и все-все-все): если бы я задал этот вопрос полгода назад, когда специализации не было ещё даже в виде RFC, то как выглядело бы решение?

Нужно именно с использованием fmt::Display, без Debug'а и именно в таком виде.

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

если бы я задал этот вопрос полгода назад, когда специализации не было ещё даже в виде RFC, то как выглядело бы решение?

Выбирай любой из двух impl. Без специализации обобщенный тип может иметь только одну реализацию типажа.

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

Можно подождать совсем немного до следующего релиза

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

anonymous
()

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

Так нельзя, потому что это - стрельба в ногу.

/thread

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

то как выглядело бы решение?

Никак?

Ну можно посмотреть как derive(Debug) устроен и что-то похожее соорудить, но именно с fmt::Display вряд ли получилось бы.

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

Как она может при таких раскладах попасть в следующий релиз?

Возможно, я наврал. Казалось, что ещё в прошлом релизе они оговаривались, что мало что успели сделать и к следующему хотят осилить "?" и специализации. Печально, если придётся ждать 1.9.

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

Почему?

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

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

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

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

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

поведение она изменять не должна.

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

Опять же, при реализации вывода для своего типа, можно делать какие-то неожиданные вещи и язык тут ничего гарантировать не может. По крайней мере, Rust.

Ну и ничего, что в плюсах специализация есть (да ещё и с перегрузкой функций) и всё нормально?

Плюс сейчас в «базовой реализации» методы надо помечать как default, если мы хотим разрешить их специализировать. Что тоже даёт несколько больше контроля.

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

Ты хочешь при помощи языка решить организационную проблему

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

Никто не помешает какому-нибудь «индусу» ... пропатчить саму библиотеку

Но индусу можно объяснить, что патчить либу - плохо. Ну, как я тебе сейчас объясняю, что специализация нужна не для того.

Ведь автор темы хочет иметь просто две разных реализации для своего типа.

Это не отменяет убогости примера.

Дык, гарантировать это нельзя.

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

Да и разве в нашем случае поведение меняется?

Меняется. Странно, что тебе это неочевидно.

в плюсах специализация есть (да ещё и с перегрузкой функций) и всё нормально

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

И вообще, я не понимаю, что ты мне пытаешься доказать.

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

Ну как сказать. Я соглашусь, что в общем случае, вполне нормально не иметь реализации Display, если Т её не имеет. Но если очень надо, то почему нет?

Если знать о тех «потенциальных проблемах», что ты перечислил, то всё будет хорошо.

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

А как надо? Debug не интересует. Изменение поведения кода не интересует. Нужно, чтобы вывод менялся в зависимости от того, реализует ли fmt::Display тот тип, от которого мы инстанцировали Edge.

Если специализация — это стрельба в ногу, то как обойтись без неё?

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

то как обойтись без неё?

Сделай виртуальный метод

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

если очень надо

Но зачем?

Если знать о тех «потенциальных проблемах»

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

то всё будет хорошо.

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

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

Нужно, чтобы вывод менялся в зависимости от того, реализует ли fmt::Display тот тип, от которого мы инстанцировали Edge.

Не нужно.

Если специализация — это стрельба в ногу

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

как обойтись без неё

Вестимо, как - созданием специализированной функции (см. Vec::extend и Vec::extend_from_slice). В твоём случае это будет выглядеть как-то так:

http://is.gd/W8WNbt

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

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

Но зачем?

Если у нас есть обобщённый код, где надо получить такое вот строковое представление, то что ты предлагаешь?

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

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

и если ты ваяешь хелворд

Или если я пишу не библиотеку. Или если я контролирую окружение или ещё 100500 условий в которых всё будет нормально.

Вообще, я соглашусь, что такое нужно весьма редко и без причин так лучше не делать. Но зачем нужен догматизм, если он только мешает жить?

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

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

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

Чтобы удобно (т. е. через print!("{}")) распечатывать массивы Edge'й от разных типов, в т. ч. от тех, для которых я не заимплементил fmt::Display (на тот случай, если мне интересен исключительно сам факт наличия ребра в таблице смежности, а не его свойства).

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

Если у нас есть обобщённый код, где надо получить такое вот строковое представление

Значит, мы по уши влезли в TMP или ещё какой-нибудь костыльный шлак, возможности для замены которого есть в Расте и нынче вводятся даже в кресты.

что ты предлагаешь?

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

Те проблемы, которые ты привёл, звучат весьма надуманно

Код ТСа тоже надуманный. Но служить иллюстрацией способен, как и мои примеры.

и к специализации никак не относятся.

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

Ещё раз - я могу хоть в каждой версии изменять единственную реализацию и ломать

А я могу не посмотреть срок годности молока и отравиться, так что мыть яблоки не нужно. Намёк ясен?

Или если я пишу не библиотеку

Тогда тебе специализация и нафиг не сдалась.

Или если я контролирую окружение

Сегодня контролируешь ты, завтра - уже не ты. Да и вообще, не переломишься столько контролировать?

Но зачем нужен догматизм

Это прагматизм. Или, по-твоему, мыть руки после туалета - тоже догматизм?

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

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

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

Чтобы удобно (т. е. через print!(«{}»)) распечатывать массивы Edge'й от разных типов

Интересовало «более высокоуровневое» «зачем». В смысле, это «просто для себя» - ну чтобы удобно посмотреть можно было? Или оно куда-то дальше пойдёт? Если первое, то Debug - это то, что нужно, как по мне.

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

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

Хамить направо и налево — это нынче в /development мода такая?

Мне плевать, будет ли там специализация или нет. FYI, тред озаглавлен «как жить без специализации». Единственное моё пожелание — сделать красиво и без бойлерплейта.

P. S.: твой пример мне не подходит, т. к. я распечатываю не Edge напрямую, а массивы Edge'й (массив реализует fmt::Display сам).

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

Debug слишком вербозен и плохо читается. У меня есть матрица смежности, которая представляет собой двумерный массив этих Edge'й, и реализация fmt::Display для этого двумерного массива рисует в консоли именно табличку, с выровненной шириной столбцов и так далее.

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

И нет, серьёзно, неужели это такое неправильное пожелание — распечатывать E(10), когда Edge инстанцирован от int'а, но E(-), когда Edge инстанцирован от какой-то НЕХ?

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

возможности для замены которого есть в Расте и нынче вводятся даже в кресты.

Насчёт крестов интересны подробности - о чём ты? Пока что вижу обратное. Скажем, хватает запросов на добавление возможности параметризировать константами и соответствующие «ишью» не закрыты.

Относятся, ибо возникли от неправильного её использования.

Хотел спорить, но вообще вопрос «филосовский». Как по мне, проблема (если она вообще есть) не в специализации, а в изначальном желании. Раст, конечно, старается не способствовать стрельбе по ногам, но часто предоставляет возможности сделать «по своему». Не будь специализации, автор мог бы через макросы что-то наворотить и не факт, что было бы лучше.

А я могу не посмотреть срок годности молока и отравиться, так что мыть яблоки не нужно. Намёк ясен?

Нет. Вообще мне это напоминает разговоры про unsafe и аргументы типа «раз unsafe есть, то значит у нас нет безопасности вообще».

Тогда тебе специализация и нафиг не сдалась.

Это ещё почему? По твоему, оптимизации для отдельных типов не могут потребоваться в рамках одного проекта?

Или, по-твоему, мыть руки после туалета - тоже догматизм?

Иногда, да. Скажем, раз уж ты любишь странные аналогии: если у нас из крана кислота течёт, то лучше не помыть, чем слепо следовать правилам.

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

Скорее натягивает плюсовые привычки на новый язык.

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

И нет, серьёзно, неужели это такое неправильное пожелание — распечатывать E(10), когда Edge инстанцирован от int'а, но E(-), когда Edge инстанцирован от какой-то НЕХ?

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

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

Хамить направо и налево — это нынче в /development мода такая?

С чего ты взял, что я хотел нахамить?

массив реализует fmt::Display

Зря. Display - для откровенно примитивных типов, для сложных типов от него пользы ноль.

сделать красиво и без бойлерплейта

Красота - дело сугубо интимное, мне вообще по душе что-то вроде:

http://is.gd/76DrOa

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

Насчёт крестов интересны подробности - о чём ты?

constexpr

Скажем, хватает запросов на добавление возможности параметризировать константами

Ты о чём? В крестах это давно есть.

проблема не в специализации, а в изначальном желании

Именно. Но это никак не противоречит моим словам.

Вообще мне это напоминает разговоры про unsafe и аргументы типа «раз unsafe есть, то значит у нас нет безопасности вообще».

Зря. Я говорю, что специализация есть, но не стоит использовать её направо и налево. Как и unsafe.

если у нас из крана кислота течёт

Я не зря раньше писал тебе про TMP. Связь с твоей аналогией понятна?

Скорее натягивает плюсовые привычки на новый язык.

Нет. Даже у трупастрауса написано, что такое специализация, для чего её использовать, а для чего - не стоит.

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

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

Могут в экзотических случаях. В 99% случаев библиотечных типов хватает, либо те оптимизации того не стоят.

Правда я предпочитать расширять это требование до «ожидаемое поведение»

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

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

где этот тип используется в структуре данных

Да плевать. При расширении функционала старый код должен работать так, как работал. Open/close principle зачем придумали?

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

constexpr

Погоди, ты говорил про «костыльный шлак», для которого в расте якобы замена есть и которая вводится в плюсы. Вот только constexpr появился раньше (стабильного) раста. Причём в последнем вычисления на этапе компиляции делаются не так просто (хотя и позволяют много больше) и до сих пор являются нестабильной частью языка.

Ты о чём? В крестах это давно есть.

Ага, а в расте нет. Собственно, спорил с «направлением развития». По некоторым параметрам наоборот раст догонят. А по другим в плюсах, к сожалению, и не собираются ничего делать.

Связь с твоей аналогией понятна?

Не совсем.

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

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

Причём в последнем вычисления на этапе компиляции делаются не так просто (хотя и позволяют много больше) и до сих пор являются нестабильной частью языка.

Ну значит, с растом я ошибся. Впрочем, это не отменяет того, что в крестах compile-time вычисления переводят на нормальное CTFE вместо костылей на TMP.

Не совсем.

А она в том, что TMP, как и кислота в кране - это крайний случай. Если такая ситуация возникла, то у тебя уже проблемы посерьёзней неправильного использования специализации/немытых рук.

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

предпочту удобный способ неудобному, но «более правильному»

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

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

Если такая ситуация возникла, то у тебя уже проблемы посерьёзней неправильного использования специализации/немытых рук.

Хочется поспорить. Конечно, метапрограммирование на шаблонах - то ещё удовольствие, но за неимением лучшего... Опять же, не уверен, что всегда можно найти решение лучше.

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

Хочется поспорить.

И опять ради спора?

но за неимением лучшего

Я и не говорил, что есть лучше. Я говорил лишь, что

метапрограммирование на шаблонах - то ещё удовольствие

Опять же, не уверен, что всегда можно найти решение лучше.

Если умело использовать кодогенерацию, нужда в TMP останется только в библиотеках, которые пытаются сделать из крестов нормальный ЯП типа буста. Довольно узкая ниша, в общем-то.

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

И опять ради спора?

Похоже на то... поэтому продолжать не буду.

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

Как она может при таких раскладах попасть в следующий релиз?

Ты был прав, не попала, а очень жаль.

Кстати, автор темы может взглянуть на забавную библиотеку specialize: http://arcnmx.github.io/specialize-rs/specialize/

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

The use of these macros requires an unstable rust compiler and the #![feature(specialization)] crate attribute.

С таким успехом я могу просто взять unstable rust compiler и заюзать нативную специализацию :)

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

С таким успехом я могу просто взять unstable rust compiler и заюзать нативную специализацию :)

Ну да... но штука всё равно забавная.

DarkEld3r ★★★★★
()

Анонимус, DarkEld3r и все-все-все: вот вы говорите, что решать эту задачу посредством специализации — это плохо, что использовать fmt::Display для получения текстового отображения сложных типов — это плохо, и так далее.

А как надо?

Вот у меня есть тип данных «граф», который реализован как матрица смежности. Есть тип данных «ребро» (то, что хранится в матрице смежности), и к нему могут быть приаттачены произвольные данные. Я хочу уметь распечатывать состояние графа. Использовать для этого fmt::Debug неудобно, т. к. будет слишком вербозно и нечитабельно.

При этом я хочу:

  • уметь выводить на печать граф, в котором присутствуют рёбра с «печатаемыми» данными (в т. ч. с теми, где fmt::Display);
  • уметь выводить на печать граф, в котором присутствуют рёбра с «непечатаемыми» данными — с прочерками вместо данных;
  • не дублировать код вывода на печать всего графа;
  • не имплементить какие-либо свои трейты для всех фундаментальных типов.

При этом я готов:

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

Как сделать это по феншую, с соблюдением этих ваших SOLID'ов и так далее?

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

вот вы говорите, что решать эту задачу посредством специализации — это плохо

Ну не совсем. Если тебя это устраивает и «так проще», то я бы не парился. Особенно, если делаешь не библиотеку.

Как сделать это по феншую, с соблюдением этих ваших SOLID'ов и так далее?

Например, как-то так:

#[derive(Debug)]
struct Data<T> {
    val: T,
}

impl<T: PrettyPrint> Data<T> {
    fn pretty_print(&self) {
        print!("Data(");
        self.val.print();
        println!(")");
    }
}

trait PrettyPrint {
    fn print(&self);
}

impl<T> PrettyPrint for T {
    default fn print(&self) {
        print!("?");
    }
}

impl<T: Display> PrettyPrint for T {
    fn print(&self) {
        print!("{}", self);
    }
}

struct NoDisplay;

fn main() {
    let d1 = Data { val: 10 };
    println!("{:?}", d1);
    d1.pretty_print();
    
    let d2 = Data { val: NoDisplay };
    //println!("{:?}", d2);
    d2.pretty_print();
}
Но повторюсь - ничего ужасного (с рядом оговорок) не вижу и при решении «в лоб». И да, я не какой-то там мега эксперт в раст, а тоже «только учусь».

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

Не. Ты не понял. Я хотел увидеть, как это делается без специализации, чтобы «добавление трейта Display для какого-то типа не меняло вывод в другом конце проекта».

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

Я хотел увидеть, как это делается без специализации

Наверное, никак.

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

не дублировать код вывода на печать всего графа;

Боже мой, там всего кода - пяток строчек. Но если ты такой DRY-фанатик, то всегда можешь удариться в функциональщину и сделать что-то вроде:

http://is.gd/dcCmnh

Чем-то не устраивает?

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

В моём случае код вывода двумерного массива выравнивает столбцы по ширине. Там кода куда больше, чем «пяток строчек».

Но да — я тоже решил передавать функцию и сделать две обёртки. Спасибо за подтверждение не-долбанутости идеи.

P. S.: да, я DRY-фанатик.

intelfx ★★★★★
() автор топика
Последнее исправление: intelfx (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.