я тут сначала наступил, а потом и в стандарте вычитал (8.3.6 Default aguments), что оказывается нельзя задавать дефолтные значения параметров при определении типа указателя на функцию.
то есть хочется писать примерно вот такой код:
typedef void (*pfun)(int a, int b = 7); // error!void f1(int a, int b);
void main(){
pfun f = f1;
f(1);
}
собственно, мне непонятна причина такого запрета. мне кажется, что никаких неоднозначностей тут не возникнет и вообще компилятору не составит труда такой код компилять.
в чем причина такого ограничения?
Здравствуйте, waev, Вы писали:
W>коллеги,
W>я тут сначала наступил, а потом и в стандарте вычитал (8.3.6 Default aguments), что оказывается нельзя задавать дефолтные значения параметров при определении типа указателя на функцию. W>то есть хочется писать примерно вот такой код: W>
W>typedef void (*pfun)(int a, int b = 7); // error!
W>void f1(int a, int b);
W>void main(){
W> pfun f = f1;
W> f(1);
W>}
W>
W>собственно, мне непонятна причина такого запрета. мне кажется, что никаких неоднозначностей тут не возникнет и вообще компилятору не составит труда такой код компилять. W>в чем причина такого ограничения?
в том, что смешиваются compile-time с run-time -- информация о том какое значение у дефолтного параметра никак не отражается на сигнатуре функции.
более того, дефолтный параметр может быть очень и очень не простым (вызов конструктора к примеру, у которого тоже могут быть дефолтные параметры, у которого... ну ты понял %))
Здравствуйте, zaufi, Вы писали:
Z>Здравствуйте, waev, Вы писали:
W>>коллеги,
W>>я тут сначала наступил, а потом и в стандарте вычитал (8.3.6 Default aguments), что оказывается нельзя задавать дефолтные значения параметров при определении типа указателя на функцию. W>> ...
компилятору не составит труда такой код компилять. W>>в чем причина такого ограничения?
Z>в том, что смешиваются compile-time с run-time -- информация о том какое значение у дефолтного параметра никак не отражается на сигнатуре функции. Z>более того, дефолтный параметр может быть очень и очень не простым (вызов конструктора к примеру, у которого тоже могут быть дефолтные параметры, у которого... ну ты понял %))
нет, я не понял. чем это принципиально отличается от объявления функции с дефолтными параметрами?
подстановку дефолтных параметров, вместе с вызовом их конструкторов, компилятор делает непосредственно при вызове функции, при чем здесь рантайм?
для вызываемой функции нет разницы были ли переданы аргументы по умолчанию или заданы явно.
Здравствуйте, waev, Вы писали:
W>нет, я не понял. чем это принципиально отличается от объявления функции с дефолтными параметрами?
В следующем коде
void f(int a=1){}
f — это не тип, это фактически "значение" с типом void(int).
С этим конкретным "значением", могут быть ассоциированы параметры по умолчанию, никак не влияя на тип.
Здравствуйте, waev, Вы писали:
W>Здравствуйте, zaufi, Вы писали:
Z>>в том, что смешиваются compile-time с run-time -- информация о том какое значение у дефолтного параметра никак не отражается на сигнатуре функции. Z>>более того, дефолтный параметр может быть очень и очень не простым (вызов конструктора к примеру, у которого тоже могут быть дефолтные параметры, у которого... ну ты понял %))
W>нет, я не понял. чем это принципиально отличается от объявления функции с дефолтными параметрами?
W>подстановку дефолтных параметров, вместе с вызовом их конструкторов, компилятор делает непосредственно при вызове функции, при чем здесь рантайм?
W>для вызываемой функции нет разницы были ли переданы аргументы по умолчанию или заданы явно.
ну от есть.
я имею в виду, что, по-моему, с точки зрения компилятора, разницы нет, вызывать ли функцию напрямую (с указанными у нее в объявлении параметрами по умолчанию) или через указатель (с указанными в определении типа этого указателя параметрами по умолчанию).
в частности, разницы нет потому, что вызываемая функция не может отличить, были ли параметры переданы явно или их подставил туда компилятор, взяв из определения функции.
Здравствуйте, waev, Вы писали:
W>Здравствуйте, zaufi, Вы писали:
Z>>Здравствуйте, waev, Вы писали:
W>>>коллеги,
W>>>я тут сначала наступил, а потом и в стандарте вычитал (8.3.6 Default aguments), что оказывается нельзя задавать дефолтные значения параметров при определении типа указателя на функцию. W>>> ... W>компилятору не составит труда такой код компилять.
ну вот побудь компилятором чуток:
чтобы помнить, что функция имеет дефолтные параметры, нужно эту информацию заманглить в тип! чтобы когда настанет момент вызова функции, через указатель к примеру, засунытый в потроха bind'a, можно было "вспомнить" о том, что туда надо передать еще кроме того, что было передано явно, но тогда получится, что
void foo(int);
// иvoid foo(int = 7);
НЕОДИНАКОВЫЕ ФУНКЦИИ -- ибо тип различется. и вообще в таком случае разрешение (resolve) перегрузок из сложного станет станет АЦЦКИ сложным (и едва ли возможным). это не говоря уже о том, что конструирование дефолтных параметров, тоже может быть далеко не тривиальным... и засунуть это в тип функции ... блин ну попробуй вот такое заманглить, раз ты умнее компилятора:
#include <iostream>
int z = 0;
int foo(int x)
{
return 2 * x;
}
int bar(int x, int y = foo(z))
{
return x + y;
}
int main()
{
z = 2;
std::cout << bar(1) << std::endl;
return 0;
}
или вот тебе шаблон, получающий параметром callable type как это делают std алгоритмы (for_each или там transform к примеру) -- как бы ты передал им дефолтные параметры? как бы ты вообще узнал о них? из типа?
W>>>в чем причина такого ограничения?
Z>>в том, что смешиваются compile-time с run-time -- информация о том какое значение у дефолтного параметра никак не отражается на сигнатуре функции. Z>>более того, дефолтный параметр может быть очень и очень не простым (вызов конструктора к примеру, у которого тоже могут быть дефолтные параметры, у которого... ну ты понял %))
W>нет, я не понял. чем это принципиально отличается от объявления функции с дефолтными параметрами?
вот чем. с точки зрения непосредственного вызова любой из этих функций -- они все воидные... им можно ничо не передавать. но здравый смысл говорит что это все разные функции с разной сигнатурой (кроме goo и foo)...
W>подстановку дефолтных параметров, вместе с вызовом их конструкторов, компилятор делает непосредственно при вызове функции, при чем здесь рантайм?
он может это сделать ТОЛЬКО при явном вызове функции... но не по указателю, потому что в типе НЕТ информации о дефолтных параметрах и как видишь ЕЕ ТАМ БЫТЬ НЕ ДОЛЖНО... иначе КАБЗДА ВСЕМУ
W>для вызываемой функции нет разницы были ли переданы аргументы по умолчанию или заданы явно.
К.О.?
Здравствуйте, waev, Вы писали:
W>коллеги,
W>я тут сначала наступил, а потом и в стандарте вычитал (8.3.6 Default aguments), что оказывается нельзя задавать дефолтные значения параметров при определении типа указателя на функцию. W>то есть хочется писать примерно вот такой код: W>
W>typedef void (*pfun)(int a, int b = 7); // error!
W>void f1(int a, int b);
W>void main(){
W> pfun f = f1;
W> f(1);
W>}
W>
W>собственно, мне непонятна причина такого запрета. мне кажется, что никаких неоднозначностей тут не возникнет и вообще компилятору не составит труда такой код компилять. W>в чем причина такого ограничения?
W>спасибо.
А давай с точки зрения здравого смысла. Есть функция у которой есть default arguments, ты определяешь тип с другими, какие использовать? Даже если это разрешимо — это просто пулемет в ногу.
Фактически, это мы дали уникальное имя типу void(int,int=7). И завернули туда вычисление необязательных аргументов. Сделали работу за компилятора.
Правда, мы не можем теперь привести pfun обратно к void(int,int) или void(int), или к точно такой же структуре, но под другим именем, — но можем привести и к std::function<void(int,int)>, и к std::function<void(int)>.
Почему это не поддержано в стандарте?
Во-первых, потому же, почему голый сишный указатель на функцию нельзя получить из std::function (наоборот — можно). Здесь не только семейство сигнатур описывается (void(int), void(int,int)), но и выражения для вычисления аргументов. А это уже в один указатель не вместится. Либо это будет указатель не на функцию, а на таблицу функций, или на какой-нибудь хитрый диспетчерский код. И никакой совместимости со старым добрым Си.
Во-вторых, поддержка этой фичи — это синтаксический сахар, сравнимый по объёму с лямбдами. Сколько лет потребовалось, чтобы лямбды в стандарт вошли? Сколько потребуется, чтобы ввести полиморфные лямбды? Вот то-то же. Если очень хочется, — нужно писать заявку в комитет и в ньюсгруппу.
Но это всё возвышенные рассуждения.
Практически, если такую фичу захотелось в конкретном месте, — то нужно глядеть в корень: ЗАЧЕМ её захотелось. Возможно, что там есть другие и не менее изящные решения в рамках нынешних возможностей.
Чисто в качестве альтернативы — совершенно не уверен, что это решает искомую задачу, но просто для иллюстрации, что так можно делать — введём не один, а два указателя рядом
void f1(int,int);
void f2(int,int);
int main()
{
void (*ff)(int,int) = (rand()%2 ? f1 : f2); // если мы захотим вызывать с 2 аргументами
std::function<void(int)> f = std::bind(ff, _1, 7); // если захотим вызывать с 1 аргументом
// если ff не нужен сам по себе - просто подставим его внутрь bind
f(1);
ff(1,2);
}
Здравствуйте, Кодт, Вы писали: К>Чисто в качестве альтернативы — совершенно не уверен, что это решает искомую задачу, но просто для иллюстрации, что так можно делать — введём не один, а два указателя рядом К>
К> void (*ff)(int,int) = (rand()%2 ? f1 : f2); // если мы захотим вызывать с 2 аргументами
К> std::function<void(int)> f = std::bind(ff, _1, 7); // если захотим вызывать с 1 аргументом
К> // если ff не нужен сам по себе - просто подставим его внутрь bind
К>
Здравствуйте, waev, Вы писали:
W>я тут сначала наступил, а потом и в стандарте вычитал (8.3.6 Default aguments), что оказывается нельзя задавать дефолтные значения параметров при определении типа указателя на функцию. W>то есть хочется писать примерно вот такой код: W>
W>typedef void (*pfun)(int a, int b = 7); // error!
W>void f1(int a, int b);
W>void main(){
W> pfun f = f1;
W> f(1);
W>}
W>
W>собственно, мне непонятна причина такого запрета. мне кажется, что никаких неоднозначностей тут не возникнет и вообще компилятору не составит труда такой код компилять. W>в чем причина такого ограничения?
Потому, что если снять *только* это ограничение, не сделав больше никаких изменений в языке, то станут возможны довольно странные сценарии использования:
typedef void (*pfun1)(int a, int b = 1);
typedef void (*pfun2)(int a, int b = 2);
void f3(int a, int b = 3);
pfun1 p1 = f3; //???
pfun2 p2 = p1; //???
Какие дополнительные изменения в языке ты предлагаешь сделать, чтобы избежать этих очевидных несуразностей?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, pzhy, Вы писали:
P>А давай с точки зрения здравого смысла. Есть функция у которой есть default arguments, ты определяешь тип с другими, какие использовать? Даже если это разрешимо — это просто пулемет в ногу.
ну это-то как раз можно, правда не в одной области видимости, а в разных. в смысле, вот такой код вполне себе работает
void f(int x, int y = 5)
{
}
void a()
{
void f(int x, int y = 2);
f(1);
}
void b()
{
void f(int x, int y = 7);
f(1);
}
Здравствуйте, rg45, Вы писали:
R>Потому, что если снять *только* это ограничение, не сделав больше никаких изменений в языке, то станут возможны довольно странные сценарии использования:
R>
R>typedef void (*pfun1)(int a, int b = 1);
R>typedef void (*pfun2)(int a, int b = 2);
R>void f3(int a, int b = 3);
R>pfun1 p1 = f3; //???
R>pfun2 p2 = p1; //???
R>
R>Какие дополнительные изменения в языке ты предлагаешь сделать, чтобы избежать этих очевидных несуразностей?
а что тут неоднозначного? типы-то у всех трех этих сущностей (p1, p2 и f3) одинаковые, различаются только дефолтные аргументы которые компилятор подставит при вызове.
по-моему всё просто, не?
Здравствуйте, waev, Вы писали:
R>>Потому, что если снять *только* это ограничение, не сделав больше никаких изменений в языке, то станут возможны довольно странные сценарии использования:
R>>
R>>typedef void (*pfun1)(int a, int b = 1);
R>>typedef void (*pfun2)(int a, int b = 2);
R>>void f3(int a, int b = 3);
R>>pfun1 p1 = f3; //???
R>>pfun2 p2 = p1; //???
R>>
R>>Какие дополнительные изменения в языке ты предлагаешь сделать, чтобы избежать этих очевидных несуразностей?
W>а что тут неоднозначного? типы-то у всех трех этих сущностей (p1, p2 и f3) одинаковые, различаются только дефолтные аргументы которые компилятор подставит при вызове. W>по-моему всё просто, не?
Все верно, тип один и тот же, и неоднозначности нет. А вот насчет простоты я сомневаюсь. Мне кажется, приведенные инициализации выглядят весьма странно, затрудняют чтение кода и могут приводить к путанице.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Кодт, Вы писали:
К>Почему это не поддержано в стандарте? К>Во-первых, потому же, почему голый сишный указатель на функцию нельзя получить из std::function (наоборот — можно). Здесь не только семейство сигнатур описывается (void(int), void(int,int)), но и выражения для вычисления аргументов. А это уже в один указатель не вместится. Либо это будет указатель не на функцию, а на таблицу функций, или на какой-нибудь хитрый диспетчерский код. И никакой совместимости со старым добрым Си. К>Во-вторых, поддержка этой фичи — это синтаксический сахар, сравнимый по объёму с лямбдами. Сколько лет потребовалось, чтобы лямбды в стандарт вошли? Сколько потребуется, чтобы ввести полиморфные лямбды? Вот то-то же. Если очень хочется, — нужно писать заявку в комитет и в ньюсгруппу.
ну вот я тоже к такому же мнению склоняюсь, что такую штуку можно самостоятельно на шаблонах и bind-ах реализовать, и поэтому дядьки из комитета не стали её вводить, что бы не усложнять язык.
(я просто думал, что, ну, может там может возникает какое-нибудь противоречие или неоднозначность в коде, которое мне не очевидно).
Здравствуйте, waev, Вы писали:
W>Здравствуйте, Кодт, Вы писали:
К>>Почему это не поддержано в стандарте? К>>Во-первых, потому же, почему голый сишный указатель на функцию нельзя получить из std::function (наоборот — можно). Здесь не только семейство сигнатур описывается (void(int), void(int,int)), но и выражения для вычисления аргументов. А это уже в один указатель не вместится. Либо это будет указатель не на функцию, а на таблицу функций, или на какой-нибудь хитрый диспетчерский код. И никакой совместимости со старым добрым Си. К>>Во-вторых, поддержка этой фичи — это синтаксический сахар, сравнимый по объёму с лямбдами. Сколько лет потребовалось, чтобы лямбды в стандарт вошли? Сколько потребуется, чтобы ввести полиморфные лямбды? Вот то-то же. Если очень хочется, — нужно писать заявку в комитет и в ньюсгруппу.
W>ну вот я тоже к такому же мнению склоняюсь, что такую штуку можно самостоятельно на шаблонах и bind-ах реализовать, и поэтому дядьки из комитета не стали её вводить, что бы не усложнять язык. W>(я просто думал, что, ну, может там может возникает какое-нибудь противоречие или неоднозначность в коде, которое мне не очевидно).
Ну вот насчет "вводить", если верить вот этому http://gcc.gnu.org/onlinedocs/gcc-4.2.1/gcc/Deprecated-Features.html#Deprecated-Features (The use of default arguments in function pointers, function typedefs and other places where they are not permitted by the standard is deprecated and will be removed from a future version of G++.), то в гцц это было уже достаточно давно. Ну и как мне кажется, когда поддерживаются дефолтные параметры для функций, то поддержка дефолтных параметров для указателей получается сама собой. Её наоборот специально выкорчевывать надо. Естественно, речь не идет про включение параметра в тип. Только о использовании параметра в момент вызова. Компилятор же видит объявление указателя в момент вызова, и из объявления берет дефолтные значения.
А вот причины, почему эту фичу из стандарта исключили мне не до конца понятны. Ну то есть да, единственный вариант -- это неоднозначность, которая возникает при присвоении указателей на функции с разными дефолтными параметрами.
А неоднозначность, которую можно получить если указать разные дефолтные параметры при объявлении одной и той же функции в разных единицах трансляции, просто так не исключишь. Ну то есть, если я правильно понимаю, если объявления в разных единицах трансляции, то компилятор про них никак не узнает, поэтому починить это поведение не удастся. Поэтому починили то, что смогли.
Но да, какая-то слабая версия на самом деле получается. Должен быть какой-то серьезный подводный камень при использовании дефолтных параметров у указателей.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Evgeny.Panasyuk, Вы писали:
К>
К>auto&& d = defaults(f, mandatory, mandatory, ondemand( []()->int { foo()+bar() } )); // первые два обязательные, третий вычисляется каждый раз
К>
mandatory могут идти только в начале или вперемешку? "В начале" уже работает.
Для ленивых вычислений вырисовывается отличие от обычных defaults:
struct Data
{
int v[16];
};
Data &f(Data &&d=Data())
{
return d;
}
void use(Data &d)
{
d.v[0]=1;//...
}
int main()
{
use(f());
}
Тут заимствуется и возвращается память со стэка вызывающей функции.
В случае с defaults_set — такой финт не пройдёт. "Заимствование" должен делать первый operator() который вызывается.
К>>auto&& d = defaults(f, mandatory, mandatory, ondemand( []()->int { foo()+bar() } )); // первые два обязательные, третий вычисляется каждый раз
К>>
EP>mandatory могут идти только в начале или вперемешку? "В начале" уже работает.
Только в начале — вперемешку непонятно, как разруливать.
А, я протупил — у тебя в defaults перечисляются только последние, необязательные аргументы. А сколько спереди обязательных — это уже следствие из сигнатуры функции.
EP>Для ленивых вычислений вырисовывается отличие от обычных defaults: EP>Тут заимствуется и возвращается память со стэка вызывающей функции. EP>В случае с defaults_set — такой финт не пройдёт. "Заимствование" должен делать первый operator() который вызывается.
В порядке безумной идеи: а что, если к defauls::operator() в хвост подверстать необязательный аргумент какого-то специального типа, в котором и разместить все вычисленные на месте аргументы.
Что-то вот этакое
struct the_defaults
{
function<Data&(int,Data&&)> fun;
function<int()> make_arg1;
function<Data()> make_arg2;
Data& operator() ( int i, Data&& d ) { fun(i,d); }
Data& operator() ( int i, storage<Data> s = uninitialized ) { s.init(make_arg2()); return fun(i, s.get<0>()); }
Data& operator() ( storage<int,Data> s = uninitialized ) { s.init(make_arg1(), make_arg2()); return fun(s.get<0>(), s.get<1>()); }
};
// где storage - это кортеж, чьи элементы конструируются не сразу, а во второй фазе;
// в роли storage<...> может пойти optional<tuple<...>>int main()
{
the_defaults d = { foo, []()->int{return rand();}, []()->Data{return Data(1,2,3);} };
use(d());
}
Здравствуйте, Кодт, Вы писали:
К>Только в начале — вперемешку непонятно, как разруливать.
Я подумал про перетасовку. Например если только второй default = 111 то:
d(1,2);
d(1,2,3);
вывод:
1 111 2
1 2 3
по идее — реализуемо.
К>А, я протупил — у тебя в defaults перечисляются только последние, необязательные аргументы. А сколько спереди обязательных — это уже следствие из сигнатуры функции.
да, либо non-type template parameter в defaults_n (наверное количество аргументов у функтора можно вытащить через SFINAE).
EP>>Для ленивых вычислений вырисовывается отличие от обычных defaults: EP>>Тут заимствуется и возвращается память со стэка вызывающей функции. EP>>В случае с defaults_set — такой финт не пройдёт. "Заимствование" должен делать первый operator() который вызывается. К>В порядке безумной идеи: а что, если к defauls::operator() в хвост подверстать необязательный аргумент какого-то специального типа, в котором и разместить все вычисленные на месте аргументы. К>Что-то вот этакое
[...] К>// где storage — это кортеж, чьи элементы конструируются не сразу, а во второй фазе; К>// в роли storage<...> может пойти optional<tuple<...>>