Comments 95
std::map<char, int> myMap;
myMap.insert(std::make_pair('a', 10));
2. Почему ты решил, что этот auto сможет правильно разобрать, что нужно?
class A
{
A(int, int);
};
class B
{
B(int, int);
};
class C
{
C(std::pair<int, int>);
C(A);
C(B);
};
C obj = Auto(10, 10); // ???
struct A {
A(signed char v): v(v){}
A(unsigned char v): v(v){}
int v;
};
A a(1); //???
если у вас несколько неявных кандидатов, то уж извините
struct A {
explicit A(signed char v): v(v){}
explicit A(unsigned char v): v(v){}
int v;
};
A a(1); // ошибка компиляции
A a('a'); // ошибка компиляции
A a((unsigned char)'a'); .// ok
и проблема решена.
А теперь посмотрим на случай с этим auto
struct A
{
explicit A(int, int);
};
struct B
{
explicit B(int, int);
};
struct C
{
explicit C(const std::pair<int, int>&);
explicit C(const A&);
explicit C(const B&);
};
std::vector<C> vec;
vec.push_back(A(10, 10));
vec.push_back(B(10, 10));
vec.push_back(std::make_pair(10, 10));
vec.push_back(Auto(10, 10)); // ??? что здесь будет
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>
Все прекрасно сработает именно в С++11. Синтаксис {}
в данном случае не имеет никакого отношения к std::initializer_list
.
Сработает, потому что это не std::initializer_list
.
В обычной инициализации структур используется тот же синтаксис и никаких проблем не возникает, хотя типы разные. (Это было еще в С.)
struct S {int a; const char *b;};
S s = {1, "2"};
std::map<int, std::string> map;
map.emplace(1, "text1");
http://cpp.sh/6rkh
Метод insert шаблонного класса map ожидает четко указанный тип, но нам приходится писать «pair<char, int>» снова и снова при каждом вызове. Хорошо если наш тип простой, а если там шаблон на шаблоне и шаблоном погоняет?
Для таких случаев есть синтаксис универсальной инициализации.
Вместо вашего громоздкого
myMap.insert(auto('a', 10));
можно написать так myMap.insert({'a', 10});
Мне кажется, что auto лишь усложнит процесс отладки, ведь так?
Если интересно, почему лучше использовать auto, чем не использовать — советую ознакомиться с 5 главой книги «Effective Modern C++» Скотта Майерса — она целиком посвящена этому ключевому слову и примерам его использования.
К тому же, есть случаи, когда auto используется для неявного вывода типа, например:
auto z = x + y, где типы переменных x и y являются шаблонными.
Или std::map<std::string, std::tuple<int, float>>::const_iterator до auto в цикле for.
А вот писать auto x = 1 нельзя — за такое надо бить по рукам.
void 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();
...
}
По вашему второй вариант лучше? Повторять длинное описание типа каждый раз, когда потребуется объявить переменную производного типа?
Тем более, что тип переменной очевиден.
using TextValueMap = std::map<std::string, std::tuple<int, float>>;
void f(TextValueMap& tvmap)
{
TextValueMap::iterator Uter = tvmap.begin();
/*...*/
}
Зачем? std::map::begin()
всегда возвращает iterator
или const_iterator
.
Если через какое то время вы поменяете сигнатуру функции f
на
void f(const TextValueMap& tvmap)
то вам придется изменять также строку
TextValueMap::iterator Uter = tvmap.begin();
на
TextValueMap::const_iterator Uter = tvmap.begin();
Компилятор конечно подскажет, но зачем отвлекаться?
Использование auto
в данном случае не вносит никаких неоднозначностей, упрощает чтение кода и облегчает его поддержку. Так зачем усложнять?
QMap<int,short> m; m[0]=1; m[1]=2;
for (auto & i: m) {
qDebug() << i; // 1 2
}
Это достаточно логично с точки зрения поведения контейнера, но не самое ожидаемое поведение с точки зрения программиста, и может быть источником проблем, особенно, в шаблонах.
Мне, кстати, интересно, как такого эффекта удалось добиться.
auto it = _map.begin();
Как тут тип понять какой тип у мапы? Что если _map — константная и const_iterator вернёт? И это очень простой пример.
Я стараюсь применять auto как можно реже и только там, где тип очевиден из контекста максимум несколькими строками выше. Например, вот в таких конструкциях:
auto thePointer = dynamic_cast<TYPE *>(theBasePointer);
Что касается циклов с итераторам — на мой взгляд, не так часто в прикладном коде возникает необходимость использования итераторов напрямую. Чаще range-based, где не нужно использовать итераторы. Логику, где нужно оперировать итераторами напрямую, можно спрятать в функции (и, как правило, это обосновано дизайном).
В данном случае auto действительно удобно. iCpu, как я понял, имеет в виду, что есть достаточное количество ситуаций, в которых auto делает код менее очевидным. Например, если бы map в вашем примере был не аргументом функции прямо на строчку выше, а, например, полем класса, то было бы уже не так неудобно:
А здесь уже начинает работать соглашение об именовании переменных. Не обязательно следовать правилам венгерской нотации, но переменную надо называть так, чтобы её поведение было понятно из её имени.
Как тут тип понять какой тип у мапы? Что если _map — константная и const_iterator вернёт? И это очень простой пример.
А вы тогда сразу cbegin вызывайте — проблем не будет.
auto itMax = std::max_element(begin(_records),end(_records));
А вот писать [...] нельзя — за такое надо бить по рукам.
Меня всегда интересовало — так где грань? Пока я для себя не нашел однозначного ответа.
auto highPriority = static_cast<bool>(features(w)[5]);
Единственное, на что следует обращать внимание — чтобы область видимости «auto» не выходила за пределы функции или блока. Это правило очень хорошо ограничивают ООП и обязательное разделение класса на .h и .cpp файлы — нельзя объявить метод класса, возвращающий auto, а реализацию написать в .cpp
for (const auto& item: someContainer)
На самом деле, почти всегда лучше писать
for (auto&& item: someContainer)
Если someContainer::iterator::operator*()
возвращает ссылку или константную ссылку, то item
таковым и будет, если rlalue ссылку то возвращаемое значение будет перемещено в item
без создания временного объекта.
возвращаемое значение будет перемещено в item без создания временного объекта
Чего??? item
в данном случае является ссылкой (вы пытастесь воспользоваться мейерсовской концепцией "универсальной сслыки"). Ничего в item
перемещаться, разумеется, не будет.
Никаких временных объектов не создается и в огигинальном варианте с lvalue-ссылкой.
auto 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.
Это всё-таки лямбда, а не указатель (в частности, штука с потенциально дорогим копированием).
И я очень надеюсь что она и не появится. Ибо чревато всякими неожиданными эффектами
в языке 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.
Во втором придется указывать шаблонные параметры
Еще со времен С твердили: учитесь и писать и читать type-agnostic код. В не-декларативных statements языков С и С++ не должно быть никаких упоминаний конкретных типов. Именам типов место в декларациях и только в декларациях. Type-agnostic код прекрасно читаем, надо только набраться смелости и отбросить в сторону костыли, которыми являлись постоянные упоминания имен типов.
Но дурные привычки-костыли продолжают жить: молодежь тупо настаивает на явном приведении типа результата malloc
в С или использовании имен типов в sizeof
, ибо так якобы "надо, чтобы знать с каким типом мы работаем". И несмотря на все усилия более продвинутой мировой C/C++ community, манера тащить за собой имена типов куда надо и куда не надо умирать никак не хочет, ни в С, ни в С++.
Надеюсь, что привлекательность новых возможностей современного С++ победит дурные привычки в С++. А вот как навести порядок в рядах С-шников — не ясно.
Еще со времен С твердили: учитесь и писать и читать type-agnostic код
Кто твердил и чем именно это обосновывалось?
Может, вы знаете какой-нибудь open source проект на С++, который активно использовал бы на type-agnostic код? Я бы глянул. На примере анализа кода подобного проекта можно было бы понять насколько хорош (ну, или плох) auto.
и то скорее в качестве документации
На мой взгляд, одной из главных задач языков высокого уровня является как раз-таки умение быть документацией. И чем более подробной — тем лучше. Я не говорю, что код при этом не должен быть лаконичным — слишком много текста делается код менее читабельным. Однако, как по мне, краткость не должна быть самоцелью.
На мой взгляд, несколько секунд, сэкономленные на указание полного типа, могут обернуться минутами затрат на ознакомление с кодом нового человека… Увы, как я уже писал выше, более или менее объективно рассудить нас сможет только проект, активно использующий auto. Если как-нибудь столкнусь с таким и не забуду о нашем разговоре — можно будет рассмотреть его в качестве примере.
P.S.: Кстати, с учётом частых дискуссий по теме auto (не только на хабре — среди моих знакомых программистов эта фича С++11 была воспринята наиболее противоречиво и часто вызывает жаркие споры), на мой взгляд, была бы полезной целая статья с разбором плюсов и минусов auto на примере анализа кода какого-нибудь реального open-source проекта. Возможно, подобная статья вывела бы тему использования auto из категории «религиозных» тем. Если когда-нибудь столкнусь с подобным проектом и на тот момент не будет статьи — могу написать. Как думаете, имеете смысл? Или, возможно, вы знаете какую-нибудь существующую статью аналогичного содержания?
несколько секунд, сэкономленные на указание полного типа, могут обернуться минутами затрат на ознакомление с кодом нового человека…
Не знаю у кого как, но у меня ознакомление с новой кодовой базой всегда происходит непросто. Вот только (опять же, лично мне) при этом всё равно важнее логика происходящего, а не типы. Ну серьёзно, чем поможет какой-нибудь Entity<Run<Some>>
? Да, можно скрупулёзно изучить все составляющие части, а потом ещё иерархии классов, специализации шаблонов и т.д. Но появляется риск забыть, что собственно искали. (:
Опять же, в моём опыте даже в весьма качественных проектах бывали "странные" именования у типов (и модулей). То есть, всё равно надо вникать в предметную область и код. Снова сошлюсь на личный опыт — мне проще постепенно расширять область понимания. Если закапываться в типы и иерархии, то это больше сбивает.
Справедливости ради, скажу, что IDE (в моём случае, QtCreator) не всегда может показать тип, если там "несколько уровней" auto
и это действительно бывает неудобно.
Ну и да, повторю аргумент про рефакторинг: приходилось сталкиваться с необходимостью масштабных переименований и наличие явных указаний типов здорово усложняло этот процесс.
А ещё существуют языки с динамической типизацией типа Python: там каждая переменная — это var.
У вас по всему тексту встречаются такие вот конструкции:
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 и универсальные ссылки.
Все-таки ((Types&&)args...)
— это тоже идеальная передача, и работает это так же, как std::forward()
.
Совсем нет. Сравните: С-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);
}
Видите разницу?
Это такая защита от не правильного использования. std::forward предполагает использование именно с шаблонным параметром, но если кто-то вдруг захочет странного и напишет что то вроде этого:
std::forward<int&>(5)
то сработает защита. В этом случае аргумент является rvalue, поэтому компилятор выбирает вторую перегрузку, но тип ссылка на lvalue. пример
Функциональной разницы нет.
Вторая версия функции предназначена только для того, чтобы предотвратить ошибки/хаки типа std::forward<int &>(42)
, т.е. ситуации, когда пользователь случайно или намеренно указывает "нелегальную" комбинацию типа и значения. То есть вторая версия существует только ради этого static_assert
внутри. Если бы мы не задавались целью следить за корректностью использования std::forward
, то достатчоно было бы первой перегрузки.
Я вижу только дополнительный static_assert()
и защиту от неправильного использования.
Но я не могу придумать пример, когда (T &&)value
не сработало бы как идеальная передача, если T
— тип универсальной ссылки, выбранный компилятором автоматически. Можете привести такой пример?
Но, самое главное, совершенно не ясно, почему вы проигнорировали вариантК сожалению, как я уже сказал, я не знал про списки инициализации. я думал, что это применимо только к инициализации переменных
myMap.insert({ 'a', 10 });
MyObj obj = {1, 2, 3};
и по этому мне пришлось изобретать велосипед.У типа int нет и не может быть конструкторапонятно, что у int нет конструктора, я просто привел простой пример.
Что вы хотели сказать вот этим: ((Types&&)args...)?если конечный конструктор принимает ссылку на объект, то без этого преобразования конструктор будет пытаться получить ссылку на промежуточную переменную, а не на первоначальный объект.
Спасибо всем за обсуждение, вы повысили мои познания языка. Где как не в споре рождается истина.
когда можно было просто присвоить к переменной 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 });
который в данном как раз и будет выбирать способ инициализации автоматически именно по типу параметра.
In C++14 you just write "auto auto(auto auto) { auto; }". The compiler infers the rest from context.
— Kolya Golikov (@meteokol) October 24, 2013
auto main() -> int
{
...
}
Вопрос: зачем?а вот пример, где описание типа в конце полезно:
static auto selfTypePtr() ->decltype(this);
Кстати интересный пример — статический метод использующий this
Очередная GCC-шная самодеятельность (либо просто дыра). Использование this
в объявлениях статических функций-членов запрещено языком. Clang корректно отлавливает эту ошибку. GCC пропускает, даже в режиме -pedantic-errors
.
Я понимаю. Однако спецификация языка однозначно утверждает, что 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".
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; //Что вернет такое выражение?
Это в каком это компиляторе, интересно?
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 function
Ответ: а почему бы и нет?
Универсальный конструктор Auto