Здравствуйте, Sinclair, Вы писали:
S>Для дальнейшего я буду использовать шарп, но аналогичные грабли есть и в твоих любимых плюсах. S>Сравним следующие две строки: S>
S>int a, b;
S>a = GetSomeInt();
S>GetSomeInt(out b); // в плюсах был бы соответственно &b
S>
S>Одинакова ли семантика первой и второй строчки? Нет, неодинакова. а получает значение ровно один раз, а в b происходит неопределённое количество присваиваний.
Расшифруй , пожалуйста, фразу, ввиду того, что она просто непонятна сама по себе. Что значит "в b происходит неопределённое количество присваиваний" ? В b вообще ничего не может происходить, это не код. Или ты хочешь сказать, что b здесь присваивание происходит несколько раз ? Тогда почему ?
void Func(char& p)
{
p = 'a';
}
char p;
Func(p);
Где второе присваивание p ? При вызове передается адрес, а не значение. Внутри присваивание. Больше ничего.
S>Это может играть важную роль в тех случаях, когда аргумент в процессе действия GetSomeInt изменяется еще где-то.
А если а во время a = GetSomeInt(); тоже где-то меняется ? В чем разница ? Кстати, как это они меняются в однопоточном приложении или кто им позволил без синхронизации меняться в многопоточном ?
S>И, как следствие, мы имеем разные ограничения на безопасность типов. Для a мы можем применить любой тип, совместимый по присваиванию с int, например — double.
А это уже просто специфика операции присваивания, которая прямого отношения к вызову функции не имеет . Вызов функции сам по себе, а дальше присваивание, а там правила совместимости типов
float f;
int i;
f = i; // так же можно писать ?
f = SomeIntFunc(i); // ну и так можно по той же причине
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Расшифруй , пожалуйста, фразу, ввиду того, что она просто непонятна сама по себе. Что значит "в b происходит неопределённое количество присваиваний" ? В b вообще ничего не может происходить, это не код. Или ты хочешь сказать, что b здесь присваивание происходит несколько раз ? Тогда почему ?
PD>void Func(char& p) PD>{ PD> p = 'a'; PD>}
PD>char p; PD>Func(p);
PD>Где второе присваивание p ? При вызове передается адрес, а не значение. Внутри присваивание. Больше ничего.
Даже если одно, то функция может наследить в том числе сразу за p, или перед p. А в случае char p = Func(), это не грозит.
PD>А если а во время a = GetSomeInt(); тоже где-то меняется ? В чем разница ? Кстати, как это они меняются в однопоточном приложении или кто им позволил без синхронизации меняться в многопоточном ?
Где написано что приложение однопоточное? Да и речь идет о семантике вызова, а не о том, есть ли там синхронизация. Семантика разная, в этом Sinclair прав!
PD>Где второе присваивание p ?
Неужто сам не догадываешься?
Ок, показываю (следи за руками):
void Func(int &sum, int start, int end)
{
for(int i=start; i<end; i++)
sum*= i;
}
Сколько раз присваивается sum? Заметь, кстати, что компилятор C#, в отличие от C++, отличает ref от out — для последнего он требует определяющего присванивания. Т.е. мы не знаем точно, сколько раз присваивается sum, но знаем, что не меньше одного:
void Func(out int sum, int start, int end)
{
sum=start; // тут опять тонкости definite assignment, связанные с out - компилятор не даёт программисту полагаться на начальное значение этого параметраfor(int i=start; i<end; i++)
sum*= i;
}
PD>А если а во время a = GetSomeInt(); тоже где-то меняется ? В чем разница ?
В том, что язык не даёт GetSomeInt читать промежуточные значения a в процессе работы. То есть тело GetSomeInt изолировано от a.
PD>Кстати, как это они меняются в однопоточном приложении или кто им позволил без синхронизации меняться в многопоточном?
Элементарно они меняются. Допустим, эта функция вызывает callback. А тело callback уже запросто в том же потоке меняет a непредсказуемым образом.
Про другой поток я и не говорю — тело функции Func никак не может узнать, нужно ли синхронизировать доступ к sum и если нужно, то как. Потому что sum может быть псевдонимом для локальной переменной на стеке конкретного потока, для поля объекта в динамически распределённой памяти, или вообще для глобальной статической переменной.
Поэтому типобезопасность требует точного соответствия типа алиаса и типа фактического аргумента.
PD>А это уже просто специфика операции присваивания, которая прямого отношения к вызову функции не имеет . Вызов функции сам по себе, а дальше присваивание, а там правила совместимости типов
PD>float f; PD>int i;
PD>f = i; // так же можно писать ? PD>f = SomeIntFunc(i); // ну и так можно по той же причине
в том то и дело. Писать f = SomeIntFunc(i) — можно. А вот так — тебе не даст компилятор (насколько я помню C++):
SomeIntFunc(i, &f)
При условии, что функция определена вот таким образом:
void SomeIntFunc(int a, int &b)
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
S>Даже если одно, то функция может наследить в том числе сразу за p, или перед p. А в случае char p = Func(), это не грозит.
Можно чуть точнее ? Перед p — это где, до вызова ? До этого мне дела нет, меня не интересует, чему равен p до вызова вообще. После вызова ? Поезд ушел, пишите письма.
PD>>А если а во время a = GetSomeInt(); тоже где-то меняется ? В чем разница ? Кстати, как это они меняются в однопоточном приложении или кто им позволил без синхронизации меняться в многопоточном ? S>Где написано что приложение однопоточное? Да и речь идет о семантике вызова, а не о том, есть ли там синхронизация. Семантика разная, в этом Sinclair прав!
Семантика , может, немного и отличается, но меня интересуют исключительно практические различия, а их практически нет . Проще говоря, я не вижу чем
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Здравствуйте, samius, Вы писали:
S>>Даже если одно, то функция может наследить в том числе сразу за p, или перед p. А в случае char p = Func(), это не грозит.
PD>Можно чуть точнее ? Перед p — это где, до вызова ? До этого мне дела нет, меня не интересует, чему равен p до вызова вообще. После вызова ? Поезд ушел, пишите письма.
Перед p — это по отрицательному смещению относительно адреса p. Но ключевое тут может курсивом. В здравом уме никто так делать не станет, но с другой стороны, никто и не запретит.
PD>Семантика , может, немного и отличается, но меня интересуют исключительно практические различия, а их практически нет . Проще говоря, я не вижу чем
PD>(int a, float b) F()
PD>мне может быть полезнее чем
PD>void F(int&a, float&b)
Вызвав такой метод очень легко что-нибудь где-нибудь испортить, особенно если переменные лежат не на стеке.
Если программист по сути одиночка, то он сам расставляет мины, сам их и обходит и ни на что не жалуется. Если в команде присутствуют люди, которые не способны постичь далеко идущий высший замысел, то это обычно и приносит проблемы.
Здравствуйте, samius, Вы писали:
PD>>Можно чуть точнее ? Перед p — это где, до вызова ? До этого мне дела нет, меня не интересует, чему равен p до вызова вообще. После вызова ? Поезд ушел, пишите письма. S>Перед p — это по отрицательному смещению относительно адреса p.
А мне какое дело до этого ? Если при этом p не перекрывается — на здоровье. Если перекрывается — нечего ерундой заниматься.
>Но ключевое тут может курсивом. В здравом уме никто так делать не станет, но с другой стороны, никто и не запретит.
Не запретит. Ерундой заниматься никто не запрещает, но это многими способами можно сделать
PD>>(int a, float b) F()
PD>>мне может быть полезнее чем
PD>>void F(int&a, float&b)
S>Вызвав такой метод очень легко что-нибудь где-нибудь испортить, особенно если переменные лежат не на стеке.
Какой ? Второй ? Ничего тут испортить нельзя. Если, конечно, не брать там указатели и не гонять их как попало. Но так можно везде испортить.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Здравствуйте, samius, Вы писали:
>>Но ключевое тут может курсивом. В здравом уме никто так делать не станет, но с другой стороны, никто и не запретит.
PD>Не запретит. Ерундой заниматься никто не запрещает, но это многими способами можно сделать
PD>>>(int a, float b) F()
PD>>>мне может быть полезнее чем
PD>>>void F(int&a, float&b)
S>>Вызвав такой метод очень легко что-нибудь где-нибудь испортить, особенно если переменные лежат не на стеке.
PD>Какой ? Второй ? Ничего тут испортить нельзя. Если, конечно, не брать там указатели и не гонять их как попало. Но так можно везде испортить.
Ну а первый способ таки исключает способы отрубить шашкой большой палец ноги (хоть и не себе), а следовательно полезнее.
В основном ты прав, но ИМХО все это эффекты второго порядка. Я согласен, что писать придется несколько иначе и эти эффекты надо учитывать. Но , на мой взгляд, эти отличия второго порядка недостаточны, чтобы вводить новое понятие в язык, поскольку пусть не на 100%, но на 99.9% мы уже имеем то же самое иным способом.
PD>>Какой ? Второй ? Ничего тут испортить нельзя. Если, конечно, не брать там указатели и не гонять их как попало. Но так можно везде испортить.
S>Ну а первый способ таки исключает способы отрубить шашкой большой палец ноги (хоть и не себе), а следовательно полезнее.
Меня, честно сказать, очень мало интересуют все эти проблемы "как бы чего не испортить". Не хотите испортить — пишите аккуратно и не портите. Если мне некие средства предложат, позволяющие сделать то. что я делаю сейчас, более элегантным способом при не худшей эффективности, я буду это изучать и использовать. Если же речь идет о синтаксических изысках, тотальной защите от всевозможных угроз или же неэффектиных, пусть и красивых решениях — это мне не интересно.
Здравствуйте, Pavel Dvorkin, Вы писали: PD>В основном ты прав, но ИМХО все это эффекты второго порядка. Я согласен, что писать придется несколько иначе и эти эффекты надо учитывать.
Ты, похоже, так и не понял, о чём я говорю.
Я говорю, что "несколько иначе" делает фичу "возврат множества значений через &-аргументы" необоснованно неудобной. "Эффекты", о которых я написал, должен учитывать не программист, а язык программирования. И язык таки это делает — я написал, как именно.
Но учёт этих эффектов второго порядка компилятором вынуждает его накладывать на код ограничения уже первого порядка.
Простейшая штука — результат функции, которая возвращает "целую 2d-точку", не получается присвоить "плавающей 2d-точке". В отличие от простого случая возврата единственного аргумента.
Я так думаю, что если бы в С++ запретили возвращать значения вообще, и потребовали всегда в таких случаях передавать результат через &-аргументы, то ты бы быстро оценил этот "второй порядок".
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Меня, честно сказать, очень мало интересуют все эти проблемы "как бы чего не испортить". Не хотите испортить — пишите аккуратно и не портите. Если мне некие средства предложат, позволяющие сделать то. что я делаю сейчас, более элегантным способом при не худшей эффективности, я буду это изучать и использовать. Если же речь идет о синтаксических изысках, тотальной защите от всевозможных угроз или же неэффектиных, пусть и красивых решениях — это мне не интересно.
А здесь речь как раз не об эффективности (исполнения кода), а как раз о том, что бы не испортить, не перепутать, не рисовать крестики чтобы не забыть, и т.п. Об удобвстве и безопасности, в основном.
Здравствуйте, Sinclair, Вы писали:
S>Я так думаю, что если бы в С++ запретили возвращать значения вообще, и потребовали всегда в таких случаях передавать результат через &-аргументы, то ты бы быстро оценил этот "второй порядок".
Ага, и еще после каждого вызова проверять HRESULT! Что-то это мне напоминает
Здравствуйте, samius, Вы писали:
S>А здесь речь как раз не об эффективности (исполнения кода), а как раз о том, что бы не испортить, не перепутать, не рисовать крестики чтобы не забыть, и т.п. Об удобвстве и безопасности, в основном.
Ну я и говорю, что ИМХО эти игры не стоят того, чтобы ради них менять серьезно язык.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Pavel Dvorkin, Вы писали: PD>>В основном ты прав, но ИМХО все это эффекты второго порядка. Я согласен, что писать придется несколько иначе и эти эффекты надо учитывать. S>Ты, похоже, так и не понял, о чём я говорю. S>Я говорю, что "несколько иначе" делает фичу "возврат множества значений через &-аргументы" необоснованно неудобной. "Эффекты", о которых я написал, должен учитывать не программист, а язык программирования. И язык таки это делает — я написал, как именно.
Да все я понял, просто я считаю эти эффекты маловажными.
S>Но учёт этих эффектов второго порядка компилятором вынуждает его накладывать на код ограничения уже первого порядка. S>Простейшая штука — результат функции, которая возвращает "целую 2d-точку", не получается присвоить "плавающей 2d-точке". В отличие от простого случая возврата единственного аргумента.
Присвоить все равно нельзя. Можно лишь получить результат как целую точку, а потом через конструктор получить плавающую. В любом случае будет 2 объекта.
S>Я так думаю, что если бы в С++ запретили возвращать значения вообще, и потребовали всегда в таких случаях передавать результат через &-аргументы, то ты бы быстро оценил этот "второй порядок".
Ну и что ?
class IPoint {
public :
int x, y;
};
class FPoint {
public :
float x, y;
FPoint(IPoint& ip)
{
x = ip.x;
y = ip.y;
}
};
IPoint GetPoint()
{
IPoint p;
p.x = p.y = 0;
return p;
}
void GetPointByRef(IPoint& ip)
{
ip.x = ip.y = 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
FPoint fp = GetPoint();
IPoint ip;
GetPointByRef(ip);
FPoint fp1 = ip;
}
Здравствуйте, Pavel Dvorkin, Вы писали: S>>Простейшая штука — результат функции, которая возвращает "целую 2d-точку", не получается присвоить "плавающей 2d-точке". В отличие от простого случая возврата единственного аргумента. PD>Присвоить все равно нельзя. Можно лишь получить результат как целую точку, а потом через конструктор получить плавающую. В любом случае будет 2 объекта.
Что значит "всё равно"? Еще раз привожу примитивный пример:
double f = GetSomeInt(); // работает
(double f) = GetSomeInt(); // не работает
// мы помним, что строчка выше - всего лишь извращённый плод моего сознания, алиас для
GetSomeInt(&f); // не работает, потому что &double не приводится к &int
Меня не интересует, что там происходит "за кадром". Код расширения инта до флоата подставлен компилятором. Если я поменяю там float на double, или, упаси байт, на какой-нибудь extended, то мне не придётся переписывать код. (Под "там" я имею в виду декларацию f — а она может случиться в совершенно другом месте. Например, это поле какой-то структуры, которой я параметризовал шаблонную функцию, в которой сделан вызов GetSomeInt()). И я всё еще имею гарантии статической проверки совместимости типов.
И вся эта могучая магия, надёжно отлаженная в поколениях компиляторов, мгновенно испаряется в тот момент, когда я хочу получить два значения вместо одного.
А вот более реалистичный пример
(int modulo, int reminder) = IntDivide(arg, divisor);
// не работает, если arg и divisor - byte;
// при очевидном определении IntDivide как template<classname T>
void IntDivide(T arg, T divisor, T &modulo, T &remainder)
// вместо этого мне нужно вручную выписывать нудные промежуточные присваивания
// хотя вот такой код будет работать безо всяких проблем:int modulo = arg / divisor; // здесь сработает очевидное расширение байта до интаint remainder = arg % divisor; // но делить придётся дважды, вместо быстрой операции, которая получает сразу оба числа.
Вот для таких случаев люди и хотят иметь возможность описывать туплы как общий случай возвращаемых скаляров.
PD>Ну и что ?
PD>Принципиальной разницы не вижу.
Ну да. Только я хочу иметь возможность всю эту кунсткамеру ликвидировать движением брови — то, на что я хочу тратить одну строчку, ты предлагаешь выписывать в целую страницу. На которой, кстати, ты сделал две ошибки (возможно, намеренных), и при этом всё еще не поддерживаешь IPoint<double>.
Понятно, что мой пример с modulo и remainder решается путём введения именованного класса DivisionResult, с соответствующими конструкторами и т.п — аналогично твоему примеру.
Но это всё — мучительный boilerplate. Хочется-то получить всё то же самое забесплатно, а не так, чтобы приходилось всё-всё писать руками.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Pavel Dvorkin, Вы писали:
S>Для дальнейшего я буду использовать шарп, но аналогичные грабли есть и в твоих любимых плюсах. S>Сравним следующие две строки: S>
S>int a, b;
S>a = GetSomeInt();
S>GetSomeInt(out b); // в плюсах был бы соответственно &b
S>
S>Одинакова ли семантика первой и второй строчки? Нет, неодинакова. а получает значение ровно один раз, а в b происходит неопределённое количество присваиваний.
В Аде в подобном случае наблюдается полная эквивалентность.
И ИМХО не надо вводить в язык дополнительные сущности, пока не станет ясно, что без них обходиться нельзя. Именно нельзя. А не "мне хотелось бы это написать в одну строку, а не 3"
PD>>Ну и что ?
PD>>Принципиальной разницы не вижу. S>Ну да. Только я хочу иметь возможность всю эту кунсткамеру ликвидировать движением брови — то, на что я хочу тратить одну строчку, ты предлагаешь выписывать в целую страницу.
Меня мало интересует количество строк.
>На которой, кстати, ты сделал две ошибки (возможно, намеренных), и при этом всё еще не поддерживаешь IPoint<double>.
Я привел минимальный демонстрационный пример, который показывает тебе суть того, о чем я говорю, и я вовсе не собирался в нем писать больше, чем мне нужно для этого примера.
S>Но это всё — мучительный boilerplate. Хочется-то получить всё то же самое забесплатно, а не так, чтобы приходилось всё-всё писать руками.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>А меня именно это интересует, а все эти синтаксические тонкости — не очень. Я свою точку зрения подробно объяснил вот здесь
Это не синтаксис, а семантика.
PD>И ИМХО не надо вводить в язык дополнительные сущности, пока не станет ясно, что без них обходиться нельзя. Именно нельзя. А не "мне хотелось бы это написать в одну строку, а не 3"
И каков критерий "нельзя"? К примеру — зачем вообще ввели какие-то шаблоны? Ведь можно очень удобно просто написать столько версий функции Max(), сколько типов аргументов нас интересует — не так ли?
Собственно, единственное, что делают шаблоны — сокращают объем записи для функций типа Max. Ничего другого они не делают.
А если копнуть чуть глубже, то окажется, что безо всех "дополнительных сущностей", внесённых в язык С++ со времён ассемблера, можно обойтись. Только строчек на нём потребуется немножко больше.
Да, на всякий случай поясню: я не предлагаю "тащить в язык всё, что в голову придёт". Любая фича должна начинать с "-500 баллов", чтобы этот барьер мешал попаданию мусора. Только если фича реально полезна, она наберёт баллов и попадёт в Долину Исполнения. Но твои критерии, имхо, чрезмерно строги.
На всякий случай напомню, что, как хорошо известно еще со времён тезиса Чёрча, обойтись можно вообще практически безо всего. Первый же тьюринг-полный язык программирования автоматически сделал все остальные языки ненужными. Поэтому мерятся они не тем, что "нового" можно сделать на этом языке, а тем, насколько мало усилий нужно для написания "старого". 100% фич реальных языков программирования сделаны только для сокращения количества строчек, и больше ни для чего.
PD>Я привел минимальный демонстрационный пример, который показывает тебе суть того, о чем я говорю, и я вовсе не собирался в нем писать больше, чем мне нужно для этого примера.
Ну, я так и подумал. Впрочем, мой аргумент ты очень хорошо подчеркнул — даже "минимальный демонстрационный" пример получился достаточно громоздким, чтобы объяснить сомневающимся все преимущества нормального решения.
PD>Бесплатный сыр бывает только в мышеловках
Ну-ну. Предлагаю отказаться от бесплатного сыра типа компайл-тайм проверки типов или перегрузки операторов. Наверно, это мышеловка — надо вернуться в старый добрый K&R C без этих новомодных фишек вроде описания типов аргументов прямо в месте декларации функции.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.