Например:
auto f = [](){}; //указатель на функцию
auto r = foo(10); //тип возвращаемый функцией foo
for (auto i = 0; i < 10; i++){}
… и т.д. То есть в левой части равенства у нас автоматический тип auto, а в правой части значение четко определенного типа. А теперь представим, что у нас все наоборот:
int a = auto(10);
Слева у нас четко описанный тип, а справа что-то неизвестное. Конечно в данном примере нет смысла вызывать универсальный конструктор, когда можно было просто присвоить к переменной a значение 10:
int a = 10;
Или в крайнем случае вызвать его конструктор:
int a(10);
А если это аргумент функции, например:
str::map<char, int> myMap;
myMap.insert(pair<char, int>('a', 10));
Метод insert шаблонного класса map ожидает четко указанный тип, но нам приходится писать «pair<char, int>» снова и снова при каждом вызове. Хорошо если наш тип простой, а если там шаблон на шаблоне и шаблоном погоняет? Тут нам поможет автоматический конструктор:
myMap.insert(auto('a', 10));
Функция, Конструктор или Оператор auto, не важно что это, создаст нам какой-то объект, который подходит под описание входного параметра метода insert.
Но к сожалению в языке C++ пока нет такой методики создания объектов, но я надеюсь, что когда-нибудь она появится, а пока хочу представить свой вариант реализации такой задачи. Конечно же основная цель упростить написание кода, но не менее важная задача не навредить нашей программе: она не должна увеличится в объеме, замедлиться в выполнении и т.п. В идеале она должна быть идентична той, что если бы мы писали без конструктора auto.
И так. Нам нужно создать какой-то универсальный объект, который бы мог преобразоваться в запрашиваемый тип и сделать это на этапе компиляции. Конечно я не беру во внимание оптимизацию компиляции O0, Og и т.п. возьмем оптимизацию Os.
Наш объект (контейнер) должен принять все входные аргументы, сохранить их у себя, а при преобразовании к запрашиваемому типу попытаться вызвать его конструктор с передачей всего, что когда-то было передано ему.
Для начала нам понадобится универсальная переменная, способная хранить как копии объектов, так и указатели и ссылки. Хотя указатель и копия в данном случае одно и тоже, а вот со ссылкой сложнее:
template<typename T>
struct Value {
constexpr Value(T v): v(v) {}
constexpr T get() {return v;}
T v;
};
template<typename T>
struct Value<T&> {
constexpr Value(T& v): v(&v) {}
constexpr T& get() {return *v;}
T* v;
};
Здесь все относительно просто, принимаем любой аргумент и сохраняем его копию, а в случае со ссылкой преобразуем ее в указатель и обратно.
Сама специализация необходима, чтобы отсеять аргументы по ссылке, чтобы создать универсальность, а вот без преобразования в указатель компилятор отказывается выполнять на этапе компиляции, хотя казалось бы в чем разница, храним мы в указателе или в ссылке.
Теперь нам нужно создать универсальный контейнер с неограниченным числом аргументов произвольных типов:
template<typename... Types>
struct Container {
constexpr Container() {}
template<typename T> constexpr operator T() {return get<T>();}
template<typename T, typename... Values> T constexpr get(Values&&... v) {return T((Values&&)v...);}
};
template<typename Type, typename... Types>
struct Container<Type, Types...> {
constexpr Container(const Type&& arg, const Types&&... args): arg(arg), args((Types&&)args...) {}
template<typename T> constexpr operator T() {return get<T>();}
template<typename T, typename... Values> T constexpr get(Values&&... v) {return args.get<T>((Values&&)v..., arg.get());}
Value<Type> arg;
Container<Types...> args;
};
Рекурсивный контейнер принимает неограниченное число аргументов различных типов и помещает первый аргумент к себе, а остальные во вложенный контейнер с оставшимися аргументами, пока не дойдем до последнего аргумента.
Также этот контейнер имеет оператор преобразования к любому требуемому типу вызывая рекурсивный метод get с вложением всех имеющихся аргументов.
Все аргументы передаются в качестве rvalue аргументов до самого конечного получателя Value, чтобы не потерять ссылки.
Ну и наконец сам универсальный конструктор Auto. Я его назвал с большой буквы, т.к. ключевое слово auto, сами понимаете, уже занято. А учитывая, что эта функция выполняет роль конструктора заглавная буква ей к лицу.
template<typename... Types>
constexpr Container<Types...> Auto(Types&&... args) {return Container<Types...>((Types&&)args...);}
Напоследок переместим класс Value в private область класса Container и получится следующее:
template<typename... Types>
struct Container {
constexpr Container() {}
template<typename T> constexpr operator T() {return get<T>();}
template<typename T, typename... Values> T constexpr get(Values&&... v) {return T((Values&&)v...);}
};
template<typename Type, typename... Types>
struct Container<Type, Types...> {
constexpr Container(const Type&& arg, const Types&&... args): arg(arg), args((Types&&)args...) {}
template<typename T> constexpr operator T() {return get<T>();}
template<typename T, typename... Values> T constexpr get(Values&&... v) {return args.get<T>((Values&&)v..., arg.get());}
private:
template<typename T>
struct Value {
constexpr Value(T v): v(v) {}
constexpr T get() {return v;}
T v;
};
template<typename T>
struct Value<T&> {
constexpr Value(T& v): v(&v) {}
constexpr T& get() {return *v;}
T* v;
};
Value<Type> arg;
Container<Types...> args;
};
template<typename... Types>
constexpr Container<Types...> Auto(Types&&... args) {return Container<Types...>((Types&&)args...);}
Все преобразование выполняется на этапе компиляции и ни чем не отличается от прямого вызова конструкторов.
Правда есть небольшие неудобства — ни один суфлер не сможет вам предложить варианты входных аргументов.
Комментарии (95)
Akon32
24.11.2016 13:37+6В С++11 есть списки инициализации и можно писать так:
std::map<char,int> m{{'a',9}}; m.insert({'b',10});
bataloviluxa
24.11.2016 21:59Не сработает в С++11. Тип std::initializer_list может хранить только один тип данных.
auto t = {1.0, 2.0, 3.0};//t — std::initializer_list.
В стандарт хотят(или уже) включить автоматическое создание пары и кортежа
auto pair = {'a',9};//std::pair<char,in>
auto cort = {'a',9,10.0};//std::tupple<char,in,double>Antervis
25.11.2016 06:09+2но синтаксис через {} относится не только к спискам инициализации. В некоторых контекстах это как раз-таки создание объекта через конструктор с подходящей сигнатурой.
TheCalligrapher
25.11.2016 09:12+2Все прекрасно сработает именно в С++11. Синтаксис
{}
в данном случае не имеет никакого отношения кstd::initializer_list
.
HolyBlackCat
25.11.2016 11:07Сработает, потому что это не
std::initializer_list
.
В обычной инициализации структур используется тот же синтаксис и никаких проблем не возникает, хотя типы разные. (Это было еще в С.)
struct S {int a; const char *b;}; S s = {1, "2"};
diggaz
25.11.2016 11:07+1можно использовать метод emplace, который сразу вызовет конструктор:
std::map<int, std::string> map; map.emplace(1, "text1");
http://cpp.sh/6rkh
Daniro_San
24.11.2016 13:48+2Метод insert шаблонного класса map ожидает четко указанный тип, но нам приходится писать «pair<char, int>» снова и снова при каждом вызове. Хорошо если наш тип простой, а если там шаблон на шаблоне и шаблоном погоняет?
Для таких случаев есть синтаксис универсальной инициализации.
Вместо вашего громоздкого
можно написать такmyMap.insert(auto('a', 10));
myMap.insert({'a', 10});
ilyaplot
24.11.2016 14:54+1Еще немного, и C++ с PHP поменяются местами.
Мне кажется, что auto лишь усложнит процесс отладки, ведь так?Jigglypuff
24.11.2016 15:03+3На самом деле нет.
Если интересно, почему лучше использовать auto, чем не использовать — советую ознакомиться с 5 главой книги «Effective Modern C++» Скотта Майерса — она целиком посвящена этому ключевому слову и примерам его использования.rikert
24.11.2016 16:58-2На самом деле, процесс отладки auto только усложняет, по сути функция auto сводится к сокрытию ужасающего нагромождения шаблонных костылей. Если я использую какую-либо стороннюю библиотеку то хочу видеть что это за переменная, так я смогу понять какие операции с ней я смогу сделать. Нагромождение шаблонов и без того это скрывает, а auto вообще сводит на нет желание разбираться в чужом коде, и надо уповать только на хорошую документацию, что бывает далеко не всегда.
DistortNeo
24.11.2016 17:25+6А просто не нужно использовать auto так, когда оно приводит к усложнению читаемости кода. Тип переменной должен быть очевиден из одной строчки.
К тому же, есть случаи, когда auto используется для неявного вывода типа, например:
auto z = x + y, где типы переменных x и y являются шаблонными.rikert
24.11.2016 19:54+1К сожалению нужно-не нужно, это не более чем демагогия об идеальном программном коде. Сколько бы минусов не поставили мне и плюсов вам, код не станет лучше, а auto только будет сбивать с толку и усложнять код. Длинный и запутанный тип нельзя простым укорачиванием до слова auto упростить и сделать ясным и понятным.
DistortNeo
24.11.2016 21:54+2Ну почему же? Упростить decltype(std::declval() + std::declval()) до auto вполне можно.
Или std::map<std::string, std::tuple<int, float>>::const_iterator до auto в цикле for.
А вот писать auto x = 1 нельзя — за такое надо бить по рукам.rikert
24.11.2016 22:11-1На правах своего мнения скажу, что C++ он вообще такой, он именно и состоит из пересекающихся шаблонов, и если программист не умеет их читать какими бы большими они не были то ему надо научиться. Мне проще читать std::map<std::string, std::tuple<int, float>>::const_iterator, я понимаю что это, для чего это и что с этим я могу сделать, я сразу вижу свойства, а увидев auto мне надо рыть, что там вообще такое в принципе или анализировать нижеследующий код и пытаться по операциям с этой auto предсказать что это за тип. Auto может быть полезен для новичков, но они впоследствии столкнутся с этой же проблемой. В общем на мой взгляд auto не сочетается с природой C++. А так да, вроде бы удобно, но только на бумаге.
Antervis
25.11.2016 06:11+1а вы увидев begin/end над контейнером не понимаете, что он возвращает итератор?
Deosis
25.11.2016 07:21+1void f(std::map<std::string, std::tuple<int, float>>& map) { auto it = map.Begin(); std::map<std::string, std::tuple<int, float>>::const_iterator end = map.End(); ... }
По вашему второй вариант лучше? Повторять длинное описание типа каждый раз, когда потребуется объявить переменную производного типа?
Тем более, что тип переменной очевиден.iCpu
25.11.2016 08:03Я не противник auto, мне это ключевое слово нравится, но, справедливости ради, в данном случае я бы переписал код так:
using TextValueMap = std::map<std::string, std::tuple<int, float>>; void f(TextValueMap& tvmap) { TextValueMap::iterator Uter = tvmap.begin(); /*...*/ }
rotor
25.11.2016 11:58+3Зачем?
std::map::begin()
всегда возвращаетiterator
илиconst_iterator
.
Если через какое то время вы поменяете сигнатуру функцииf
на
void f(const TextValueMap& tvmap)
то вам придется изменять также строку
TextValueMap::iterator Uter = tvmap.begin();
на
TextValueMap::const_iterator Uter = tvmap.begin();
Компилятор конечно подскажет, но зачем отвлекаться?
Использованиеauto
в данном случае не вносит никаких неоднозначностей, упрощает чтение кода и облегчает его поддержку. Так зачем усложнять?iCpu
25.11.2016 12:44-1Мой ответ относился не к использованию слова auto в коде, а к сокращению размеров записи с помощью выражений using, так как предыдущий комментатор высказался именно на тему объёмов писанины. Ключевое слово auto, всё-таки, создано не только и не столько для этого.
И оффтопомВ Qt5 проход по контейнерам с ключами в новом стиле for даст весьма забавный эффект. В следующем коде i будет типа short, а не QPair <int,short>, как было бы с std::map и std::pair<int,short>, соответственно.
QMap<int,short> m; m[0]=1; m[1]=2; for (auto & i: m) { qDebug() << i; // 1 2 }
Это достаточно логично с точки зрения поведения контейнера, но не самое ожидаемое поведение с точки зрения программиста, и может быть источником проблем, особенно, в шаблонах.
Мне, кстати, интересно, как такого эффекта удалось добиться.Antervis
25.11.2016 13:18+1И оффтопом
там итератор странно реализован: operator * вместо пары ключ/значение возвращает значение, но имеет методы key/value
semenyakinVS
25.11.2016 13:46В данном случае auto действительно удобно. iCpu, как я понял, имеет в виду, что есть достаточное количество ситуаций, в которых auto делает код менее очевидным. Например, если бы map в вашем примере был не аргументом функции прямо на строчку выше, а, например, полем класса, то было бы уже не так неудобно:
auto it = _map.begin();
Как тут тип понять какой тип у мапы? Что если _map — константная и const_iterator вернёт? И это очень простой пример.
Я стараюсь применять auto как можно реже и только там, где тип очевиден из контекста максимум несколькими строками выше. Например, вот в таких конструкциях:
auto thePointer = dynamic_cast<TYPE *>(theBasePointer);
Что касается циклов с итераторам — на мой взгляд, не так часто в прикладном коде возникает необходимость использования итераторов напрямую. Чаще range-based, где не нужно использовать итераторы. Логику, где нужно оперировать итераторами напрямую, можно спрятать в функции (и, как правило, это обосновано дизайном).DistortNeo
25.11.2016 14:03+1В данном случае auto действительно удобно. iCpu, как я понял, имеет в виду, что есть достаточное количество ситуаций, в которых auto делает код менее очевидным. Например, если бы map в вашем примере был не аргументом функции прямо на строчку выше, а, например, полем класса, то было бы уже не так неудобно:
А здесь уже начинает работать соглашение об именовании переменных. Не обязательно следовать правилам венгерской нотации, но переменную надо называть так, чтобы её поведение было понятно из её имени.
Как тут тип понять какой тип у мапы? Что если _map — константная и const_iterator вернёт? И это очень простой пример.
А вы тогда сразу cbegin вызывайте — проблем не будет.
Antervis
25.11.2016 14:06+1вы забываете про случаи, когда тип иррелевантен в контексте использования. Например:
auto itMax = std::max_element(begin(_records),end(_records));
TheShock
25.11.2016 04:30А вот писать [...] нельзя — за такое надо бить по рукам.
Меня всегда интересовало — так где грань? Пока я для себя не нашел однозначного ответа.
Antervis
25.11.2016 06:13надо лишь добавить, что всё-таки лучше воздержаться от некоторых советов Майерса:
auto highPriority = static_cast<bool>(features(w)[5]);
4e1
24.11.2016 21:59Не особо, а, например, в случае for (auto item: someContainer) — auto вообще в самый раз — какого типа someContainer — неважно, более того его можно спокойно менять vector/list/set… не переписывая при этом весь код, где по нему итерируются.
Единственное, на что следует обращать внимание — чтобы область видимости «auto» не выходила за пределы функции или блока. Это правило очень хорошо ограничивают ООП и обязательное разделение класса на .h и .cpp файлы — нельзя объявить метод класса, возвращающий auto, а реализацию написать в .cpp4e1
24.11.2016 22:12сори, тип контейнера ни при чем, но тип элемента контейнера тоже менять можно, например всякие int, int8_t qint8 и их 16, 32, 64 сородичи и т. д.
ZekaM
25.11.2016 11:10А так не лучше будет?
for (const auto& item: someContainer)rotor
25.11.2016 12:07На самом деле, почти всегда лучше писать
for (auto&& item: someContainer)
Если
someContainer::iterator::operator*()
возвращает ссылку или константную ссылку, тоitem
таковым и будет, если rlalue ссылку то возвращаемое значение будет перемещено вitem
без создания временного объекта.Antervis
25.11.2016 13:19в некоторых случаях может резолвнуться в неконстантную ссылку, а мутабельный доступ к контейнеру с implicit shared memory (например, как в контейнерах Qt) приведет к лишнему копированию данных
TheCalligrapher
25.11.2016 19:34возвращаемое значение будет перемещено в item без создания временного объекта
Чего???
item
в данном случае является ссылкой (вы пытастесь воспользоваться мейерсовской концепцией "универсальной сслыки"). Ничего вitem
перемещаться, разумеется, не будет.
Никаких временных объектов не создается и в огигинальном варианте с lvalue-ссылкой.
semenyakinVS
25.11.2016 14:05-1Хм. Если имеется в виду range-based цикл, я совсем не понимаю почему тут-то нельзя указать тип содержимого контейнера. Места займёт не так много — не нужно городить шаблонные конструкции с итераторами. А код понятнее станет. Сразу ясно, что смысл: мы перебираем элементы некой коллекции. Не важно какой, важно элементы какого типа в ней лежат (и это действительно важно — ведь мы в теле цикла будем оперировать объектами этого типа).
semenyakinVS
27.11.2016 04:45Если не секрет — объясните, что я не так написал? Я серьёзно. Не хотел никого троллить или флудить. Действительно хочу понять — какой смысл прятать тип элементов коллекции в range-based циклах (если речь действительно о них шла)?
0xd34df00d
27.11.2016 04:51+1А смысл в этих типах? В конце концов, вам важно лишь то, что вы с ними делаете, а это уже видно из тела цикла. Какая разница, перебираете ли вы std::string, QString или ещё что, если у вас в теле цикла
out << trim(str);
?
Если так уж хочется посмотреть на статический тип элементов — наведите курсор на соответствующий идентификатор, хорошая IDE вам покажет, а плохой не надо пользоваться.
Antervis
28.11.2016 06:16+1Чтобы, если потом окажется, что в целях оптимизации надо заменить std::list на std::vector, достаточно было поменять тип в одном месте. Меня самого совсем недавно auto спас от сотен строк кода на шаблонах или макросах (нужна была таблица, корректно отображающая одномерные/двумерные вектора любых числовых типов)
mihaild
24.11.2016 18:44+2auto f = [](){}; //указатель на функцию
#include <type_traits> int main() { auto f1 = [](){}; static_assert(std::is_pointer<decltype(f1)>::value, "f1 is not pointer"); }
fp.cpp:5:5: error: static_assert failed "f1 is not pointer" static_assert(std::is_pointer<decltype(f1)>::value, "f1 is not pointer"); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 error generated.
Это всё-таки лямбда, а не указатель (в частности, штука с потенциально дорогим копированием).Satus
25.11.2016 22:43Это всё-таки лямбда, а не указатель (в частности, штука с потенциально дорогим копированием).
В данном случае будет просто структура с определенным оператором `()`.mihaild
26.11.2016 01:19В данном случае — да. Поэтому написано «потенциально» (на практике — в случаях, когда тяжелые объекты захватываются по значению).
Dark_Daiver
24.11.2016 19:26+1>Но к сожалению в языке C++ пока нет такой методики создания объектов
И я очень надеюсь что она и не появится. Ибо чревато всякими неожиданными эффектамиDeosis
25.11.2016 07:28К счастью
в языке C++ пока нет такой методики создания объектов
Так как она никак не сочетается с шаблонами и перегрузками функций.
template<int length, typename Type> class SomeClass { public: SomeClass(Complex c); private: ... } template<int length, typename Type> void f(SomeClass<length, Type> some); f(SomeClass<10, int>(1.0)); f(Auto(1.0));//???
В первом варианте компилятор найдет неявное преобразование из double в Complex.
Во втором придется указывать шаблонные параметры
crea7or
24.11.2016 20:17-1Дико бесит когда встречаю в чужих c# исходниках var. Приходится переключаться с понимания задачи на поиск того, что в этом var хранят сейчас и auto такой же трэш.
Dark_Daiver
24.11.2016 20:21+2Зато с auto итерирование по коллекциям перестало быть болью
crea7or
24.11.2016 22:41-1Кажется что да, наверно правильно, а потом понимаешь, что чтобы узнать про тип объектов в коллекции придётся IDE напрягать.
Dark_Daiver
24.11.2016 22:49+1Обычно, где-то неподалеку есть объявление самой коллекции, а так да. Но это цена которую лично я готов заплатить за удобный for
TheCalligrapher
25.11.2016 05:01+3Еще со времен С твердили: учитесь и писать и читать type-agnostic код. В не-декларативных statements языков С и С++ не должно быть никаких упоминаний конкретных типов. Именам типов место в декларациях и только в декларациях. Type-agnostic код прекрасно читаем, надо только набраться смелости и отбросить в сторону костыли, которыми являлись постоянные упоминания имен типов.
Но дурные привычки-костыли продолжают жить: молодежь тупо настаивает на явном приведении типа результата
malloc
в С или использовании имен типов вsizeof
, ибо так якобы "надо, чтобы знать с каким типом мы работаем". И несмотря на все усилия более продвинутой мировой C/C++ community, манера тащить за собой имена типов куда надо и куда не надо умирать никак не хочет, ни в С, ни в С++.
Надеюсь, что привлекательность новых возможностей современного С++ победит дурные привычки в С++. А вот как навести порядок в рядах С-шников — не ясно.
semenyakinVS
25.11.2016 14:07-1Еще со времен С твердили: учитесь и писать и читать type-agnostic код
Кто твердил и чем именно это обосновывалось?0xd34df00d
25.11.2016 23:14+1Это трудно обосновывать, потому что это двоякое утверждение.
С моей точки зрения, во-первых, сигнатуры функций — это важно, сигнатура выражает некоторое намерение, что функция будет делать. Если у вас
boost::optional<Foo> FindFoo()
, значит, она может его не найти, и это сразу видно.
С другой стороны, после того, как я написал
auto widget = CreateAWidget();
, мне не столь важно, какой у него тип.
Типы, если хотите, это документация. Хорошо, когда она есть, и когда она точная, но совершенно не обязательно каждый раз документацию указывать в каждой точке использования соответствующей функции.
semenyakinVS
26.11.2016 04:23Вообще, я вот подумал — у меня пока не было опыта работы с крупным С++ проектом, в котором активно использовалось бы auto. Негативно отношусь к нему в большой степени из-за воспоминаний, оставленных нестрого типизированными скриптовыми языками (python и action script), большой код в которых часто воспринимался очень трудно.
Может, вы знаете какой-нибудь open source проект на С++, который активно использовал бы на type-agnostic код? Я бы глянул. На примере анализа кода подобного проекта можно было бы понять насколько хорош (ну, или плох) auto.0xd34df00d
26.11.2016 06:36+2Я сходу вряд ли готов дать релевантный пример.
С другой стороны, у меня есть опыт работы над достаточно крупными проектами на хаскеле, который статически и строго типизирован, но вместе с тем указания типов в подавляющем большинстве случаев компилятору вообще не нужны. Правило «явно писать сигнатуры только у top-level-функций, и то скорее в качестве документации» — оно хорошее и вполне рабочее.
semenyakinVS
27.11.2016 04:41и то скорее в качестве документации
На мой взгляд, одной из главных задач языков высокого уровня является как раз-таки умение быть документацией. И чем более подробной — тем лучше. Я не говорю, что код при этом не должен быть лаконичным — слишком много текста делается код менее читабельным. Однако, как по мне, краткость не должна быть самоцелью.
На мой взгляд, несколько секунд, сэкономленные на указание полного типа, могут обернуться минутами затрат на ознакомление с кодом нового человека… Увы, как я уже писал выше, более или менее объективно рассудить нас сможет только проект, активно использующий auto. Если как-нибудь столкнусь с таким и не забуду о нашем разговоре — можно будет рассмотреть его в качестве примере.
P.S.: Кстати, с учётом частых дискуссий по теме auto (не только на хабре — среди моих знакомых программистов эта фича С++11 была воспринята наиболее противоречиво и часто вызывает жаркие споры), на мой взгляд, была бы полезной целая статья с разбором плюсов и минусов auto на примере анализа кода какого-нибудь реального open-source проекта. Возможно, подобная статья вывела бы тему использования auto из категории «религиозных» тем. Если когда-нибудь столкнусь с подобным проектом и на тот момент не будет статьи — могу написать. Как думаете, имеете смысл? Или, возможно, вы знаете какую-нибудь существующую статью аналогичного содержания?0xd34df00d
27.11.2016 04:55+3Не сможет проект нас рассудить. И разборы плюсов-минусов на примере проекта тоже не смогут нас рассудить.
По крайней мере, до тех пор, пока мы с вами не придумаем объективные критерии. Свяжем количество багов на тысячу строк кода с использованием auto, например. Или среднее время от знакомства человека с проектом до первого pull request'а. Или, не знаю, время компиляции, в конце концов. Тоже объективная метрика, пусть и не столь полезная.
А читабельность для всех разная. Вам удобно знать тип элемента в range-based for loop, мне не столь обязательно. Моему коллеге удобно написать прототип на лиспе и нафигачить потом аннотаций типов, когда прототип будет готов, мне удобнее сначала продумать иерархию типов и написать сигнатуры нужных функций, а функции определить как
undefined
, а когда код начнёт тайпчекаться, выключить мозг и пойти писать тела функций.
Субъективно оно всё, короче. Можно только обсуждать, какой субъективности в среднем больше. Факт наличия этого треда показывает, что type-agnostic-субъективщины, возможно, больше.
DarkEld3r
28.11.2016 14:09+1несколько секунд, сэкономленные на указание полного типа, могут обернуться минутами затрат на ознакомление с кодом нового человека…
Не знаю у кого как, но у меня ознакомление с новой кодовой базой всегда происходит непросто. Вот только (опять же, лично мне) при этом всё равно важнее логика происходящего, а не типы. Ну серьёзно, чем поможет какой-нибудь
Entity<Run<Some>>
? Да, можно скрупулёзно изучить все составляющие части, а потом ещё иерархии классов, специализации шаблонов и т.д. Но появляется риск забыть, что собственно искали. (:
Опять же, в моём опыте даже в весьма качественных проектах бывали "странные" именования у типов (и модулей). То есть, всё равно надо вникать в предметную область и код. Снова сошлюсь на личный опыт — мне проще постепенно расширять область понимания. Если закапываться в типы и иерархии, то это больше сбивает.
Справедливости ради, скажу, что IDE (в моём случае, QtCreator) не всегда может показать тип, если там "несколько уровней"
auto
и это действительно бывает неудобно.
Ну и да, повторю аргумент про рефакторинг: приходилось сталкиваться с необходимостью масштабных переименований и наличие явных указаний типов здорово усложняло этот процесс.
DistortNeo
24.11.2016 22:05+1Ох как вы будете тогда злиться от новых возможностей C# 7 (анонимные классы).
А ещё существуют языки с динамической типизацией типа Python: там каждая переменная — это var.crea7or
24.11.2016 22:39Когда пишу на языках с var'ами — я к этому готов, они изначально таковы, а в с++ мне это кажется лишним. Если это используют там, где предполагается — то ничего особо страшного(да и не особо нужно-то). Но ведь будут куда ни попадя auto писать.
0xd34df00d
25.11.2016 23:09Не надо в одну кучу мешать динамическую типизацию и неявные аннотации типов, это вообще две ортогональные вещи.
Лучше бы хаскель вспомнили.
rotor
24.11.2016 21:07+1У вас по всему тексту встречаются такие вот конструкции:
template<typename... Types> constexpr Container<Types...> Auto(Types&&... args) {return Container<Types...>((Types&&)args...);}
Что вы хотели сказать вот этим: ((Types&&)args...)?
Мне кажется, в данном случае будет уместнее использоватьstd::forward
:
template<typename... Types> constexpr Container<Types...> Auto(Types&&... args) {return Container<Types...>(std::forward(args)...);}
Выглядит так, как будто вы замышляли идеальную передачу, а выполняете странное приведение типов в стиле C.
кроме того, вот эта фраза
Все аргументы передаются в качестве rvalue аргументов до самого конечного получателя Value, чтобы не потерять ссылки.
Явно свидетельствует, что вы путаете rvalue и универсальные ссылки.
HolyBlackCat
25.11.2016 17:54Все-таки
((Types&&)args...)
— это тоже идеальная передача, и работает это так же, какstd::forward()
.rotor
25.11.2016 19:40Совсем нет. Сравните: С-style-cast и std:forward
1) When the C-style cast expression is encountered, the compiler attempts to interpret it as the following cast expressions, in this order:
a) const_cast<new_type>(expression);
b) static_cast<new_type>(expression), with extensions: pointer or reference to a derived class is additionally allowed to be cast to pointer or reference to unambiguous base class (and vice versa) even if the base class is inaccessible (that is, this cast ignores the private inheritance specifier). Same applies to casting pointer to member to pointer to member of unambigous non-virtual base;
c) static_cast (with extensions) followed by const_cast;
d) reinterpret_cast<new_type>(expression);
e) reinterpret_cast followed by const_cast.Возможная реализация для std:forward (g++ bits/move.h):
template<typename T> constexpr T&& forward(typename std::remove_reference<T>::type& t) noexcept { return static_cast<T&&>(t); } template<typename T> constexpr T&& forward(typename std::remove_reference<T>::type&& t) noexcept { static_assert(!std::is_lvalue_reference<T>::value, "template argument substituting T is an lvalue reference type"); return static_cast<T&&>(t); }
Видите разницу?
DistortNeo
25.11.2016 21:17Мне всегда было интересно, в каких случаях (реальных) возможно срабатывание assert во втором forward и почему нельзя просто писать static_cast<T&&> вместо std::forward?
rotor
25.11.2016 23:04Это такая защита от не правильного использования. std::forward предполагает использование именно с шаблонным параметром, но если кто-то вдруг захочет странного и напишет что то вроде этого:
std::forward<int&>(5)
то сработает защита. В этом случае аргумент является rvalue, поэтому компилятор выбирает вторую перегрузку, но тип ссылка на lvalue. пример
TheCalligrapher
25.11.2016 23:11Функциональной разницы нет.
Вторая версия функции предназначена только для того, чтобы предотвратить ошибки/хаки типа
std::forward<int &>(42)
, т.е. ситуации, когда пользователь случайно или намеренно указывает "нелегальную" комбинацию типа и значения. То есть вторая версия существует только ради этогоstatic_assert
внутри. Если бы мы не задавались целью следить за корректностью использованияstd::forward
, то достатчоно было бы первой перегрузки.
HolyBlackCat
29.11.2016 22:42Я вижу только дополнительный
static_assert()
и защиту от неправильного использования.
Но я не могу придумать пример, когда
(T &&)value
не сработало бы как идеальная передача, еслиT
— тип универсальной ссылки, выбранный компилятором автоматически. Можете привести такой пример?
Ivanbk
24.11.2016 21:52Но, самое главное, совершенно не ясно, почему вы проигнорировали вариант
К сожалению, как я уже сказал, я не знал про списки инициализации. я думал, что это применимо только к инициализации переменных
myMap.insert({ 'a', 10 });
и по этому мне пришлось изобретать велосипед.MyObj obj = {1, 2, 3};
У типа int нет и не может быть конструктора
понятно, что у int нет конструктора, я просто привел простой пример.
Что вы хотели сказать вот этим: ((Types&&)args...)?
если конечный конструктор принимает ссылку на объект, то без этого преобразования конструктор будет пытаться получить ссылку на промежуточную переменную, а не на первоначальный объект.
Спасибо всем за обсуждение, вы повысили мои познания языка. Где как не в споре рождается истина.
TheCalligrapher
24.11.2016 21:59когда можно было просто присвоить к переменной a значение 10
int a = 10
— это не присваивание, а инициализауция.
Или в крайнем случае вызвать его конструктор:
У типа
int
нет и не может быть конструктора. Конструкторы в языке С++ бывают только у класс-типов.int
— это не класс-тип.
Более того, статься совершенно не объясняет, зачем все это нужно. Пример с
map::insert
— мимо кассы, обо эта задача в С++ давно решается униформной инициализацией
myMap.insert({ 'a', 10 });
Причем этот вариант превосходит все предложенные варианты тем, что сразу создает пару правильного типа —
std::pair<const char, int>
— обратите внимание на наличиеconst
передchar
. Забывать указывать этотconst
— популярный огрех среди начинающих программистов. Это приводит к лишней промежуточной конверсии. К сожалению, от аналогичной проблемы же страдает и популярный вариант сmake_pair
. И от такой же проблемы страдает и ваш вариант.
Но, самое главное, совершенно не ясно, почему вы проигнорировали вариант
myMap.insert({ 'a', 10 });
который в данном как раз и будет выбирать способ инициализации автоматически именно по типу параметра.
TheCalligrapher
24.11.2016 22:40+1…
Также вызывает недоумение ваше
auto f = [](){}; //указатель на функцию
Хоть "captureless" лямбды и приводимы к указателю на функцию, в данном случае
f
— отнюдь не указатель на функцию, а все таки специальный функциональный объект (closure object).
elephanten
24.11.2016 21:59В С++ есть масса замечательных вещей, которые умиляют. На мой взгляд, первым идет оператор «сиськи» operator()(), а вторым — ваши три пары разных скобок подряд [](){}.
AxisPod
25.11.2016 06:03-3Что за бред я только что прочитал? Вы предлагаете функциональность, которая позволит сломать какой-либо код написав что-то в другом месте. Вы случайно не любитель садо-мазо?
komissarex
25.11.2016 12:31+3DistortNeo
27.11.2016 05:13Только что увидел такой код:
Вопрос: зачем?auto main() -> int { ... }
Ivanbk
27.11.2016 07:53+1Скорее всего для того, чтобы выпендриться.
а вот пример, где описание типа в конце полезно:static auto selfTypePtr() ->decltype(this);
Кстати интересный пример — статический метод использующий thisTheCalligrapher
28.11.2016 00:21Очередная GCC-шная самодеятельность (либо просто дыра). Использование
this
в объявлениях статических функций-членов запрещено языком. Clang корректно отлавливает эту ошибку. GCC пропускает, даже в режиме-pedantic-errors
.Ivanbk
28.11.2016 05:23А мне не нужен this, мне нужна только декларация. Функция никогда не будет вызываться, у нее даже тела нет. С помощью ее я могу узнать собственный тип там где обычно это невозможно.
TheCalligrapher
28.11.2016 07:17Я понимаю. Однако спецификация языка однозначно утверждает, что
this
"shall not appear within the declaration of a static member function", даже несмотря на то, что "its type and value category is defined within a static member function as it is within a non-static member function".Ivanbk
28.11.2016 08:33Вот это явная дыра:
template<int value> struct B { enum {VALUE = value}; }; struct A { int v1; int v2; static auto offset() -> B<(int)&this->v2 - (int)&this->v1>; }; ... decltype(A::offset())::VALUE; //Что вернет такое выражение?
Antervis
28.11.2016 09:00ошибку компиляции вестимо
Ivanbk
28.11.2016 09:14В том то и дело, что нет, но результат всегда будет 0.
TheCalligrapher
28.11.2016 09:19Это в каком это компиляторе, интересно?
GCC:
error: 'this' is not a constant expression
Clang:
error: non-type template argument is not a constant expression
note: use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member functionIvanbk
28.11.2016 09:52ну, в частности у меня gcc version 4.9.2 (AVR_8_bit_GNU_Toolchain_3.5.3_1700) на других архитектурах и версиях не проверял
Antervis
28.11.2016 06:12во многих других языках тип возвращаемого значения в функции указывается в конце объявления.
DeadKnight
1. С парой все просто
2. Почему ты решил, что этот auto сможет правильно разобрать, что нужно?
Ivanbk
А в чем разница с этим:
struct A {
A(signed char v): v(v){}
A(unsigned char v): v(v){}
int v;
};
A a(1); //???
если у вас несколько неявных кандидатов, то уж извините
DeadKnight
Дык, дело то в том, что в таком случае я напишу
и проблема решена.
А теперь посмотрим на случай с этим auto