Pull to refresh

Comments 32

Сколько всего понапридумывали люди, нет чтобы просто добавить именованные параметры в язык и не мучаться! Я в каком-то давнем обсуждении даже синтаксис предлагал — unary dot notation.
void foo(int x=1, int y=2, int z=3) {}
foo(.y=100, .z=200, .x = 300);
foo(.z=500);


Вообще это печальная сторона С++. С одной стороны, программисты реально хотят появления новых языковых возможностей — отсюда появляются и активно используются библиотеки типа Boost. С другой стороны, новые возможности в сам язык вводить не спешат… костыли из Буста становятся все более распространенными, а затем их начинают затаскивать в стандарт, они попадают в стандарт, и получается, что вроде как и возможность реализована — но каким-то немыслимо кривым и вывернутым способом. Вместо добавления небольшого количества простого и понятного кода в компилятор добавляют огромное количество непонятного кода на шаблонах в библиотеку.
Давайте посмотрим на огромное количество непонятного кода на шаблонах, добавленного в стандартную библиотеку C++11:
Threading
Tuple types
Hash tables
Regular expressions
Smart pointers
Random number
Wrapper reference
Polymorphic wrappers for function objects
Type traits for metaprogramming

Что из этого вы бы предложили заменить простым и понятным кодом в компиляторе?
Главная фишка С++ — огромный спектр применений. С++ используется везде — от нижнего уровня взаимодействия с железом до разработки UI.
Не всем это понятно, но все «языковые возможности» кто-то где-то должен запрограммировать, и оно потом должно быть слинковано с вашей программой.
Таки образом внедрение в язык «возможностей» — обрезает эти применения до UI, т.к. делает обязательной и раздутой стандартную библиотеку.
Threading частично (как в C#),
Regular expressions (со статичной компиляцией константных рв во время компиляции программы),
Smart pointers,
Wrapper reference
PS. Промахнулся, ответ OlegMax
Не представляю, что может так не устраивать в нынешней реализации smart pointers, чтобы потребовался перенос в синтаксис.
(ответ olegmax )
Все что называется «language feature emulation», многое из «metaprogramming» и «generic programming».
Из вашего списка пожалуй type traits, может быть polymorphic wrappers for function objects.
Tuples однозначно, их нужно на уровне синтаксиса слить со списками инициализации. Тогда можно писать выражения вида {i, j, k}=10; или {i, j, k} = {10,20,30}, множественные возвраты из функий — и все красивым и естественным путем.
Еще:
Any, Optional, Variant — должна быть прямая языковая поддержка; для опционального типа взять "?" из C#.
Все что касается эмуляции концептов.
Вообще шаблоны нужно расширить так, чтобы они плавно переходили в синтаксические макросы, я пожалуй в новогодние каникулы статейку напишу по этому поводу.
Полноценные функциональные типы должны быть встроены в язык и иметь синтаксис вида (char,float)=>int
Сигналы и слоты скорее всего должны быть встроены в язык на уровне синтаксиса с реализацией по умолчанию, но с возможностью переопределять реализацию
Сопрограммы безусловно на уровне синтаксиса в язык
Ну и т.д, я всего буста не знаю к сожалению, но там несомненно найдется еще много кандидатов.

Разумеется, в виде библиотек должны остаться такие вещи как математика, файловая система, регулярные выражения, случайные числа… То есть общая идея — в язык вносится все то, для чего нужна работа с AST, типизатором, кодогенератором. Все, что не касается языка как такового — остается в виде библиотек.
Во-первых, я отвечал строго про «затаскивать в стандарт», как это сейчас происходит — выглядит все не так катастрофично. Видение развития C++ — отдельная тема.
Во-вторых, сейчас можно писать return std::make_tuple{10, 20, 30} или std::tie{i, j, k}, что, конечно, ужас-ужас-ужас по сравнению с предложенными вами вариантами. Но, с другой стороны, нам — плюсистам за это доплачивают.
UFO just landed and posted this here
>>>createArray(10, 20); // Что это значит? Что за «10»? Что за «20»?

Вот именно. Что это за хардкод? Что за мэджик намберс? Может лучше не допускать такого в коде и все будет ОК?
А вы хотите «легализовать» такой хардкод… Замазываете симптомы, а не лечите болезнь.
Речь не о хардкоде «10» и «20», а о том, что же означают первый и второй параметры функции.
Дело не в магических числах, а в порядке следования параметров. Ну назовете вы переменные «capacity» и «length», только как это поможет понять, что из этих двух методов надо вызвать:
createArray(capacity, length);
createArray(length, capacity);

Для сравнения: вот так выглядел бы вызов подобной функции в Objective-C:
[self createArrayWithLength:length andCapacity:capacity];
Именованные параметры не могут быть добавлены на уровень языка, без включения имен параметров в сигнатуру функции (сейчас в сигнатуре только типы и порядок параметров), что сильно ломает обратную совместимость, которая является одним из главных критериев для дальнейшего развития стандарта.
Конечно в N4172 предложен вариант без утери обратной совместимости, но использование такого варианта может принести вместо удобства головную боль: например автор используемой третьесторонней библиотеки решит переименовать параметры, или вообще убрать их имена — имеет полное право. А про переносимость таких программ можно будет вообще забыть.
Именованные параметры не нужно включать в сигнатуру. Значения по умолчанию не включены в сигнатуру, и имена аргументов не нужно.
Допустим:
void func(int x=0);

А теперь попробуем:
template<class F, class ... Args>
auto call(F f, Args&& ... args) {
    return f(std::forward<Args>(args)...);
}

...

call(func);

Или даже:
auto f = func;
f();


Значения по умолчанию практически ничем не отличаются от макросов, и всего-лишь делают замену на уровне препроцессора с:
func();

на:
func(0);


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

Неужели c++ недостаточно твердо стоит на двух костылях макросов и значений по умолчанию, что его еще нужно подпереть третьим?
Я что-то не пойму, Вы предлагаете сделать так, чтобы:
typedef int (*reductionFunc)(int accumulator, int value);

int myReductionFunc(int acc, int v) {
  return acc + v;
}

reductionFunc f = myReductionFunc;

давало type error из-за несоответствия имён аргументов? Или зачем тогда имена аргументов в сигнатуре?
Именно. Но я этого не предлагаю, напротив, говорю, что такой вариант невозможен, а без него именованные параметры превратятся в дополнительную препроцессорную магию, когда имена вроде как есть и используются, но вроде как и нет, так как средствами языка невозможно узнать о их существовании или отсутствии (например при помощи sfinae). Какая-то квантовая механика, а не программирование.
Во-первых, не очень понятно, где и зачем может понадобиться ориентироваться на имена аргументов при помощи SFINAE (а это то ещё костылище само по себе). Всё равно, первичной в C++ остаётся позиция аргумента, а не его имя. И так останется до тех пор, пока кардинально не изменится calling convention. Т.е. всё равно нельзя будет использовать f(int x, y) вместо g(int y, x) через, например, указатель на функцию, даже с именованными аргументами, чтобы x попадал в x, а y попадал в y в обеих функциях.

Во-вторых, хотя и можно смотреть на реализацию значений по-умолчанию как на«практически ничем не отличающуюся от макросов», однако вредных побочных эффектов макросов всё-таки нет. А отсутствие возможности использовать в SFINAE — это всё-таки отсутствие чего-то (чего и так нет), а не наличие чего-то вредного.

В-третьих, пусть такая реализация неполна, не решает 100% проблем и т.д., но хотя бы сколько-то удобства кому-то даёт, не добавляя никаких существенных вредных эффектов.

Более того, можно было бы разрешить делать что-нибудь вроде (имея в виду пример из моего предыдущего комментария):
int reduce(std::vector<int> vec, reductionFunc f) {
  int acc = 0;
  for (int v : vec) {
    acc = f(accumulator=acc, value=v);
  }
  return acc;
}
...

int r = reduce(myVec, myReductionFunc);

т.е. разрешить использовать имена аргументов из объявления типа, а не из объявления функции. Правда, сейчас GCC позволяет объявлять типы указателей на функции со значениями по умолчанию, но не позволяет эти значения использовать. В стандарт не заглядывал, поэтому не знаю, это по стандарту нельзя, не обязательно или просто ещё не доделали.
Правда, сейчас GCC позволяет объявлять типы указателей на функции со значениями по умолчанию, но не позволяет эти значения использовать.
А можно пример, как это выглядит?
Да, видимо перепутал с чем-то. Объявлять тоже нельзя. А жаль :)
Проблема вашего кода в том, что f — это указатель на функцию, а не сама функция. Поэтому, если у вас есть скажем вторая функция:
void other_func(int arg = 10);
то и call(func), и call(other_func) вызывают одну и ту же функцию
void call<void (*)(int)>( void (*f)(int) )

Логично и то, что при сохранении указателя на функцию теряется информацию о параметрах по умолчанию, иначе вы либо не смогли бы сохранить в один указатель вашу func и функцию без параметра по умолчанию, например:
void yet_another_func(int arg);
либо указатель на функцию пришлось бы делать некой нетривиальной структурой данных, которая при вызове функции выполняет нетривиальный код с возможным броском исключения в случае, если сохраненная в указателе функция аргумент по умолчанию не поддерживает.

Чтобы ваш код заработал, надо чтобы для func и other_func вызывались разные версии шаблонной функций. Этого можно достичь, например, завернув каждую функцию в свой функциональный объект:
call([](auto&&... args){ func(std::forward<decltype(args)>(args)...); });
call([](auto&&... args){ other_func(std::forward<decltype(args)>(args)...); });


Правда, как нетрудно заметить, этот код не совсем эквивалентен предыдущему, он работает не с отдельной функцией, а со всем семейством перегруженных функций с одинаковым именем. Работать с одной конкретной функцией с поддержкой аргументов по умолчанию, вроде как, нельзя, возможно, тут есть некоторая недоработка.
Проблема в том, что значение по умолчанию Шредингера находится в суперпозиции — оно одновременно есть, когда мы пытаемся вызвать функцию, опуская его; и его нет, когда мы пытаемся наблюдать его.
Чтобы ваш код заработал, надо чтобы для func и other_func вызывались разные версии шаблонной функций. Этого можно достичь, например, завернув каждую функцию в свой функциональный объект:
call([](auto&&... args){ func(std::forward<decltype(args)>(args)...); });
call([](auto&&... args){ other_func(std::forward<decltype(args)>(args)...); });


А можно просто отказаться от плохой практики использования значения по умолчанию в пользу перегрузок и вызова функций согласно их сигнатурам:
void func(int x);
void func() {
    func(0);
}

...

call(static_cast<void(&)()>(func)); // static_cast необходим для выбора перегруженного варианта
call(static_cast<void(&)(int)>(func),10);
от плохой практики использования значения по умолчанию
Честно говоря, так и не понял, чем эта практика плоха. Можно поподробней? Указатель на функцию, принимающую меньшее количество параметров, легко получить простой оберткой:
void func(int x = 0) {}
void (*ptr)() = [](){ func(); };

Ваш способ же вынуждает нас поддерживать согласованными между собой несколько функций, кроме того это всё будет некрасиво выглядеть в IDE: вместо одной сигнатуры функции с подсказками значений аргументов по умолчанию мы получим несколько функций без всяких подсказок.
UFO just landed and posted this here
Интересно а чем не угодил довольно старый но по моему мнению очень удобный подход через структуры, когда много параметров?
OpenDesc FileDesc;
FileDesc.Path = "../file.file";
FileDesc.OpenFor = READ;
FileDesc.CreateIfNotExist = true;
...
auto lv_File = OpenFile(FileDesc);

К примеру такое сделали для DirectX10,11 и по моему это очень удобно.
Ну, если поставить цель докопаться на ровном месте, то можно попенять многословностью (вон сколько раз FileDesc повторяется — в каждой строке), ну и передачей по значению (хотя легко заменить на ссылку). А вообще я согласен — вполне хороший вариант сейчас, сам пользуюсь.
Вот тут бы не помешала фича c99: designated initializer
struct Point {
  int x;
  int y;
  int z;
};

struct Point p = {
  .y = 1,
  .x = 2
};


В контексте бы C++11 могло бы получиться что-то вроде:
struct Point {
  int x = 0;
  int y = 0;
  int z = 0;
};

void some_proc(const Point &p) { ... }

some_proc({
  .x = 12,
  .z = 22
});


Со своими ограничениями, но просто получилась бы фишка без глубоких модификаций спеков и компиляторов (просто адаптировать код C99 компилятора).
Через такие костыли как представлены в статье эта фича нафиг не нужна. Тем более что именованные параметры просто синтаксический сахар, а не жизненно необходимая вещь.
По-моему, кто-то просто использует неполноценную IDE, отсюда и проблемы.
image
Единстенное, после NetBeans-а такую подсказку уж никак нельзя назвать «полноценной», скорее, ущербной. Как и в MSVS.
В контексте данной статьи на скриншоте присутствует вся необходимая информация: порядок параметров, их имена и тип.
Но все же хотелось бы увидеть пример подсказки в NetBeans (ставить ради этого лень, так что если не затруднит — будьте добры).
Sign up to leave a comment.