Здравствуйте, Кодт, Вы писали:
К>Которая плоха тем, что при несоответствии типов аргументов формирует громоздкие ошибки.
К>Вот было бы здорово как-то говорить компилятору "этот набор аргументов — такой же, как для вот такого семейства функций" К>
Здравствуйте, Кодт, Вы писали:
К>Вот было бы здорово как-то говорить компилятору "этот набор аргументов — такой же, как для вот такого семейства функций"
Да вроде можно просто static_assert добавить, что-нибудь типа:
Что правда не мешает компилятору даже после фейла static_assert'а все равно и дальше пытаться компилять и соотственно генерировать дальнийшие громоздкие ошибки. Но тут -Wfatal-errors может помочь
Здравствуйте, rg45, Вы писали:
R>Так а констрейнты чем плохи? R>Можно даже не полениться и объявить концепт:
Уже украдено до нас: std::is_constructible_v в <type_traits> и std::constructible_from в <concepts>
Плохи тем, что неудача подстановки in vivo приводит к читаемой ошибке, где перечисляются неудачные кандидаты.
Другое дело, что читаемость у shared_ptr закопана в недра — куда он тщетно пробрасывает аргументы, чтобы сконструировать объект специальным аллокатором.
А у optional она разбавлена другими конструкторами, которые тоже не подошли.
Тогда как с констрейном (хоть на requires, хоть на enable_if) она сведётся к простой фразе "нешмогла".
Здравствуйте, Voivoid, Вы писали:
V>Да вроде можно просто static_assert добавить, что-нибудь типа:
Дело в том, что не только я хочу компилятору что-то сказать, но и чтобы компилятор что-то мне содержательное сказал.
Когда у него голая неудача — он пишет причину неудачи
— не подошёл ни один кандидат из "вот посмотрите что я пробовал"
— неоднозначный выбор из "вот посмотрите что подошло"
— что-то ещё
А когда неудача завёрнута в булево выражение? "false".
Кстати, прикольно! Стандартный концепт constructible_from у gcc реализован через предикат is_constructible_v. Ну false и false.
А вот если написать руками
template<class T, class... A>
concept Constructible = requires(A... a) { T(a...); };
и по наущению gcc выставить ему флажок -fconcepts-diagnostics-depth=2
то он провалится внутрь констрейна и распишет, что именно ему не понравилось — голую неудачу.
Здравствуйте, reversecode, Вы писали:
R>вроде стандарт вроде как не регламентирует концепты на аргументы итд R>это воля разработчиков либ
R>если да R>то это и через пропозл не протянуть
Сейчас действует такая логика:
— вносить изменения в namespace std нельзя
— набор перегрузок make_unique / make_shared / optional::optional прибит гвоздями
— поэтому неудача подстановки — это в любом случае ошибка компиляции
А уж как именно эту ошибку формировать — дело десятое:
— отлуп на уровне объявления через старый добрый шаблонный sfinae
— отлуп на уровне объявления через requires
— static_assert на очень ранней стадии
— ошибка в недрах
Для пропозала здесь ещё нет места, потому что принцип замороженности std — достаточно важен.
Но вот сформулировать пропозал "давайте сделаем человекочитаемую диагностику"... было бы круто.
gcc ведь смог, при условии, что ему никто не мешает (хотя его собственная реализация стандартной библиотеки — мешает).
Ещё одно направление для пропозала... Не знаю, как точно сформулировать.
Суть вот в чём. Шаблон декоратора стирает то, как объявлены аргументы в декорируемой функции. И это может повлиять на правила выбора перегрузки.
Грубо говоря,
void f(X, Y, Z);
void f(X, Y, auto);
void f(X, auto, T) requires true; // констрейны - это тоже часть сигнатуры (хотя в ABI и не проросло ещё?)
// и декораторvoid g(auto... a) /* [[signature_like (f(a...))]] */ ;
void g(X, U, V);
Без вытаскивания перегрузок — тут сработает очень тупое правило:
— если типы аргументов не совпадают с точностью до cv& с X,U,V — то лучше подходит шаблон
— а в рамках этого шаблона могут быть и неоднозначности, и наоборот, ничего не подойдёт
То есть, вместо плоского семейства перегрузок мы получаем иерархическое. И логика из-за этого меняется.
Вроде бы в прошлом или позапрошлом году я уже развлекался с темой "как делать полиморфную функцию, диспетчеризующую россыпь лямбд".
Есть такой лайфхак (прикроем глаза на то, что типы должны различаться — это легко сделать, но обвязка будет громоздкой)
Тут мы говорим компилятору: "ну у тебя же есть сигнатуры всех операторов() у всех функциональных объектов, как-нибудь сам разрулишь"
А попробуйте подверстать аргумент, или, напротив, связать его? Ну положим, мы можем адаптировать каждую лямбду независимо
auto replace_first_with_int(auto f) {
return [f](int x, auto... a) requires requires { f(x, a...); } { f(x, a...); };
}
и потом склеить эти адаптеры
auto join_replaced_first_with_int(auto... fs) {
return join(replace_first_with_int(fs)...);
}
Вот только здесь мы прямо сразу обеспечили себе ошибку неоднозначности!
Если f1 и f2 подходят (например, у f1 тип аргумента идеально совпал, а у f2 через преобразование), то сигнатуры адаптеров — это (int, auto...) — и они подходят одинаково!
И в настоящее время я затрудняюсь сказать, как это можно красиво решить. И можно ли вообще.
и скомпилировать его в Visual Studio (не в godbolt), то компилятор формирует вот такую диагностику:
диагностика msvc-14.3
1>E:\Development\VS2022\ConsoleApplication1\main.cpp(23,4): error C2672: 'make_shared': no matching overloaded function found
1>E:\Development\VS2022\ConsoleApplication1\main.cpp(9,20):
1>could be 'std::shared_ptr<_Ty> make_shared(A &&...)'
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(23,4):
1> the associated constraints are not satisfied
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(8,10):
1> the concept 'Constructible<Foo,int,int>' evaluated to false
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(5,46):
1> '<function-style-cast>': cannot convert from 'initializer list' to 'Foo'
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(5,46):
1> 'Foo::Foo': no overloaded function could convert all the argument types
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(15,4):
1> could be 'Foo::Foo(const char *,const char *)'
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(5,46):
1> 'Foo::Foo(const char *,const char *)': cannot convert argument 1 from '_Ty' to 'const char *'
1> with
1> [
1> _Ty=int
1> ]
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(5,63):
1> Conversion from integral type to pointer type requires reinterpret_cast, C-style cast or parenthesized function-style cast
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(14,4):
1> or 'Foo::Foo(const char *,int)'
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(5,46):
1> 'Foo::Foo(const char *,int)': cannot convert argument 1 from '_Ty' to 'const char *'
1> with
1> [
1> _Ty=int
1> ]
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(5,63):
1> Conversion from integral type to pointer type requires reinterpret_cast, C-style cast or parenthesized function-style cast
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(13,4):
1> or 'Foo::Foo(int,const char *)'
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(5,46):
1> 'Foo::Foo(int,const char *)': cannot convert argument 2 from '_Ty' to 'const char *'
1> with
1> [
1> _Ty=int
1> ]
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(5,63):
1> Conversion from integral type to pointer type requires reinterpret_cast, C-style cast or parenthesized function-style cast
1> E:\Development\VS2022\ConsoleApplication1\main.cpp(5,46):
1> while trying to match the argument list '(_Ty, _Ty)'
1> with
1> [
1> _Ty=int
1> ]
В этой диагностике присутствуют интересующие нас строки:
1> the associated constraints are not satisfied
. . .
1> the concept 'Constructible<Foo,int,int>' evaluated to false
. . .
1> '<function-style-cast>': cannot convert from 'initializer list' to 'Foo'
. . .
1> 'Foo::Foo': no overloaded function could convert all the argument types
. . .
1> could be 'Foo::Foo(const char *,const char *)'
. . .
1> or 'Foo::Foo(const char *,int)'
. . .
1> or 'Foo::Foo(int,const char *)'
--
Не можешь достичь желаемого — пожелай достигнутого.
К>template<class T, class... A>
К>concept Constructible = requires(A... a) { T(a...); };
К>
Немного оффтопа: в таком виде этот концепт не будет работать для классов, конструируемых по rvalue ссылкам — по той причине, что a... в теле концепта являются lvalue выражениями. И не просто не будет работать, а его использование будет невозможным ни под каким соусом:
#include <memory>
template<class T, class... A>
concept Constructible = requires(A... a) { T(a...); };
struct Foo
{
Foo(std::unique_ptr<int>&&) {}
};
int main()
{
static_assert( ! Constructible<Foo, std::unique_ptr<int>>); // NOT constructible
static_assert( ! Constructible<Foo, std::unique_ptr<int>&&>); // NOT constructible
}
Если не хочется тащить в концепт perfect forwarding ссылки, то этого можно не делать (это вопрос семантики, которую мы хотим заложить в концепт), но использовать std::forward очень желательно в любом случае:
template<class T, class... A>
concept Constructible = requires(A... a) { T(std::forward<A>(a...)); };
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>Немного оффтопа: в таком виде этот концепт не будет работать для классов, конструируемых по rvalue ссылкам — по той причине, что a... в теле концепта являются lvalue выражениями. И не просто не будет работать, а его использование будет невозможным ни под каким соусом:
Не, ну это понятно, просто мне откровенно лень обмазывать иллюстрации форвардами.
И вообще, жалко, что в синтаксисе нет какой-нибудь короткой формы для идиомы `std::forward<decltype(arg)>(arg)`.
rvalue-ссылки — хакнули парсер, чтоб лексер не переделывать, взяли &&.
Универсальные ссылки — хакнули семантику, чтобы парсер не переделывать, взяли опять &&.
А вот forward/move уже не парились, выкатили быстрофикс в стандартную библиотеку и успокоились.