ParamType - не cv-квалифицированный указатель
#новичкам
Список постов по теме , Пост про слои
Переходим к выводу параметров для указателей. Давайте просто накидаем много указателей разных типов и посмотрим, какой тип шаблонного параметра выведется для них.
В комментах к строчкам все расписано, что и во что выводится. Я же обращу внимание на принцип. Снова возвращаемся к капусте. ParamType - указатель, а значит от типа выражения, переданного в функцию, мы должны этот указатель и оторвать и получим тип Т. Так и происходит во всех случаях. Если передаем константный указатель - снимаем этот слой вместе с константностью.
Здесь все просто, работает также как и со ссылками. Почти. Семантика сохранения константности шаблонного типа повторяется. То есть если указатель указывает на константный инт, то тип Т тоже будет константным. Однако, если константной ссылки не может быть(то что в народе называют константной ссылкой - это на самом деле ссылка на константный объект: сама по себе ссылка неизменяема, она просто может указывать на другой объект), то указатель может быть константным. То есть здесь уже играют роль слои вложенности. В этом случае, константность внутреннего слоя(который ближе к самому объекту) непосредственно отражается на шаблонном параметре Т, а константность внешнего слоя к типу Т не будет иметь отношения. Примерами здесь являются
В этом примере у типа param аж 2 слоя вложенности определены: 1 на указатель и 2 на контейнер. От типа аргумента в начале отрезаем указатель вместе с константностью, а далее и слой с std::list. По итогу тип Т выводится в то, что стоит в треугольных скобках у листа.
Есть одна интересная деталь: сигнатура функции подразумевает, что сам указатель не будет константным, то есть его можно изменять. И если вы передадите в нее константный указатель, то эта константность очень неожиданно пропадает и расплывается в пучине правил вывода типов. Так происходит с переменными
Dig deeper. Stay cool.
#template #cppcore
#новичкам
Список постов по теме , Пост про слои
Переходим к выводу параметров для указателей. Давайте просто накидаем много указателей разных типов и посмотрим, какой тип шаблонного параметра выведется для них.
template <class T>
void func(T* param) {...}
// | |
// ParamType
int x = 42;
int * p_x = &x;
const int * p_const_x = &x; // p_const_x is a ptr to const int
int * const const_p_x = &x; // const_p_x is a const ptr to int
const int * const const_p_const_x = &x; // const_p_const_x is a const ptr to const int
int ** p_p_x = &p_x; // p_p_x is a ptr to a ptr to x as int
const int * const * const const_p_const_p_const_x = &const_p_const_x; // const_p_const_p_const_x is a const ptr to a const ptr to const int
func(p_x); // T is int, param's type is int*
func(p_const_x); // T is const int, param's type is const int*
func(const_p_x); // T is int, param's type is int *
func(const_p_const_x); // T is const int, param's type is const int *
func(p_p_x); // T is int *, param's type is int **
func(const_p_const_p_const_x); // T is const int * const, param's type is const int * const *
В комментах к строчкам все расписано, что и во что выводится. Я же обращу внимание на принцип. Снова возвращаемся к капусте. ParamType - указатель, а значит от типа выражения, переданного в функцию, мы должны этот указатель и оторвать и получим тип Т. Так и происходит во всех случаях. Если передаем константный указатель - снимаем этот слой вместе с константностью.
Здесь все просто, работает также как и со ссылками. Почти. Семантика сохранения константности шаблонного типа повторяется. То есть если указатель указывает на константный инт, то тип Т тоже будет константным. Однако, если константной ссылки не может быть(то что в народе называют константной ссылкой - это на самом деле ссылка на константный объект: сама по себе ссылка неизменяема, она просто может указывать на другой объект), то указатель может быть константным. То есть здесь уже играют роль слои вложенности. В этом случае, константность внутреннего слоя(который ближе к самому объекту) непосредственно отражается на шаблонном параметре Т, а константность внешнего слоя к типу Т не будет иметь отношения. Примерами здесь являются
const_p_x, const_p_const_x, const_p_const_p_const_x.template <class T>
void func(std::list<T> * param) {...}
// | |
// ParamType
std::list<double> lst;
std::list<std::unique_ptr<const double>> lst_of_const;
std::list<std::vector<std::unique_ptr<const int>>> lst_vec_of_const;
std::list<std::vector<std::unique_ptr<const int>>> * const const_p_lst_vec_of_const = &lst_vec_of_const;
func(&lst); // T is double, param's type is std::list<double>
func(&lst_of_const); // T is std::unique_ptr<const double>, param's type is std::list<std::unique_ptr<const double>>*
func(&lst_vec_of_const); // T is std::vector<std::unique_ptr<const int>>, param's type is std::list<std::vector<std::unique_ptr<const int>>>*
func(const_p_lst_vec_of_const); // T is std::vector<std::unique_ptr<const int>>, param's type is std::list<std::vector<std::unique_ptr<const int>>>*
В этом примере у типа param аж 2 слоя вложенности определены: 1 на указатель и 2 на контейнер. От типа аргумента в начале отрезаем указатель вместе с константностью, а далее и слой с std::list. По итогу тип Т выводится в то, что стоит в треугольных скобках у листа.
Есть одна интересная деталь: сигнатура функции подразумевает, что сам указатель не будет константным, то есть его можно изменять. И если вы передадите в нее константный указатель, то эта константность очень неожиданно пропадает и расплывается в пучине правил вывода типов. Так происходит с переменными
const_p_x, const_p_const_x, const_p_const_p_const_x и const_p_lst_vec_of_const Если для нешаблонной функции с параметром неконстантного указателя при передаче в нее константного указателя была бы ошибка компиляции, то здесь эта штука проходит фэйс-контроль. Помните об этой об этой особенности и потенциальной опасности.Dig deeper. Stay cool.
#template #cppcore
Telegram
Грокаем C++
Всем привет)
Мы для вас подготовили серию статей по выводу шаблонных параметров. Когда-то давно нас попросили рассказать про конструкцию decltype(auto), но про это сложно будет рассказывать, не разобрав по отдельности decltype и auto. Но в первую очередь…
Мы для вас подготовили серию статей по выводу шаблонных параметров. Когда-то давно нас попросили рассказать про конструкцию decltype(auto), но про это сложно будет рассказывать, не разобрав по отдельности decltype и auto. Но в первую очередь…
👍13🔥7❤3❤🔥2
ParamType - cv-квалифицированный параметр
#новичкам
Список постов по теме, Пост про слои
Рассмотрим вариант, когда ParamType - cv-квалифицированная ссылка. Тогда для вывода типа верхний уровень константности аргумента остается только в типе параметра функции, а шаблонный тип выводится без этой константности на верхнем уровне. Любую ссылочность типа аргумента функции мы игнорируем.
В случае func тип param всегда будет константной ссылкой, вопрос только на что. И это что-то и будет искомым шаблонным типом. И получается он путем отбрасывания константности и ссылочности от типа аргумента функции. Для
Когда у нас в типе param появляется вложенность, то продолжаем обращаться к аналогии с капустой. Просто снимаем весь первый слой со всеми константностями и ссылочностями и оставшееся будет типом Т. На примерах все подписано.
ParamType - cv-квалифицированный указатель
Когда в статьях и книжках расписывают этот вариант, то очень часто отсылаются к такой форме параметра функции const T * param. И говорят, что в этом случае вывод типа шаблонного параметра мало отличается от случая cv-квалифицированных ссылок. И это действительно правда. С одним уточнением, что это указатель на константу, а не константный указатель.
В случае, когда указатель указывает на константный объект - ситуация действительно похожая на ссылки. Отрываем один слой указательности от типа аргумента функции, от того, что осталось убираем константность - получится искомый тип Т.
Все работает также, только добавляется прикол: если в функцию с такой сигнатурой передать константный указатель, то это не будет учитываться и param будет неконстантным указателем. Здесь как бы создается новый указатель, являющийся копией старого. Он не обязан сам быть константным, так как оригинальный поинтер остается неизменным.
Теперь рассмотрим настоящие константные указатели.
#новичкам
Список постов по теме, Пост про слои
Рассмотрим вариант, когда ParamType - cv-квалифицированная ссылка. Тогда для вывода типа верхний уровень константности аргумента остается только в типе параметра функции, а шаблонный тип выводится без этой константности на верхнем уровне. Любую ссылочность типа аргумента функции мы игнорируем.
template <class T>
void func(const T& param) {...}
// | |
// ParamType
int x = 42;
const int const_x = x;
const int& const_ref_x = x;
std::list<double> lst;
func(x); // T is int, ParamType is const int&
func(const_x); // T is int, ParamType is const int&
func(const_ref_x); // T is int, ParamType is const int&
func(lst); // T is std::list<double>, ParamType is const std::list<double>&
___________________________
template <class T>
void func1(const std::shared_ptr<T>& param) {...}
// | |
// ParamType
std::shared_ptr<double> ptr;
std::shared_ptr<const double> ptr_of_const;
const std::shared_ptr<const double>& const_ref_ptr_of_const;
func1(ptr); // T is double, param's type is const std::shared_ptr<double>&
func1(ptr_of_const); // T is const double, param's type is const std::shared_ptr<const double>&
func1(const_ref_ptr_of_const); // T is const double, param's type is const std::shared_ptr<const double>&
В случае func тип param всегда будет константной ссылкой, вопрос только на что. И это что-то и будет искомым шаблонным типом. И получается он путем отбрасывания константности и ссылочности от типа аргумента функции. Для
const_x откидываем константность, для const_ref_x - и константность и ссылочность. Для x и lst типы выводятся без изменений.Когда у нас в типе param появляется вложенность, то продолжаем обращаться к аналогии с капустой. Просто снимаем весь первый слой со всеми константностями и ссылочностями и оставшееся будет типом Т. На примерах все подписано.
ParamType - cv-квалифицированный указатель
Когда в статьях и книжках расписывают этот вариант, то очень часто отсылаются к такой форме параметра функции const T * param. И говорят, что в этом случае вывод типа шаблонного параметра мало отличается от случая cv-квалифицированных ссылок. И это действительно правда. С одним уточнением, что это указатель на константу, а не константный указатель.
В случае, когда указатель указывает на константный объект - ситуация действительно похожая на ссылки. Отрываем один слой указательности от типа аргумента функции, от того, что осталось убираем константность - получится искомый тип Т.
template <class T>
void func(const T * param) {...}
// | |
// ParamType
int x = 42;
int * p_x = &x; // p_x is a ptr to x as int
int ** p_p_x = &p_x; // p_p_x is a ptr to a ptr to x as int
const int * p_const_x = &x; // p_const_x is a ptr to x as a const int
int * const const_p_x = &x; // const_p_x is a const ptr to x as int
const int * const const_p_const_x = &x; // const_p_const_x is a const ptr to x as const int
func(p_x); // T is int, param's type is const int*
func(p_const_x); // T is int, param's type is const int*
func(const_p_x); // T is int, param's type is const int*
func(const_p_const_x); // T is const int, param's type is const int *
func(p_p_x); // T is int *, param's type is int * const *
Все работает также, только добавляется прикол: если в функцию с такой сигнатурой передать константный указатель, то это не будет учитываться и param будет неконстантным указателем. Здесь как бы создается новый указатель, являющийся копией старого. Он не обязан сам быть константным, так как оригинальный поинтер остается неизменным.
Теперь рассмотрим настоящие константные указатели.
👍11🔥4❤3👎1
template <class T>
void func(T * const param) {...}
// | |
// ParamType
int x = 42;
int * p_x = &x; // p_x is a ptr to x as int
int ** p_p_x = &p_x; // p_p_x is a ptr to a ptr to x as int
const int * p_const_x = &x; // p_const_x is a ptr to x as a const int
int * const const_p_x = &x; // const_p_x is a const ptr to x as int
const int * const const_p_const_x = &x; // const_p_const_x is a const ptr to x as const int
func(p_x); // T is int, param's type is int * const
func(p_const_x); // T is const int, param's type is const int * const
func(const_p_x); // T is int, param's type is int * const
func(const_p_const_x); // T is const int, param's type is const int * const
func(p_p_x); // T is int *, param's type is int * * const
Здесь все еще проще. param - всегда константный указатель, что бы вы в функцию не передали. А для вывода Т просто снимаем внешний слой указательности со всем квалификаторами и вуаля - ваш тип Т готов.
Самый душный блок из вывода типов готов, дальше будет по-веселее.
Believe in good future. Stay cool.
#cppcore #template
👍9❤5❤🔥4🔥4👎1🤯1
ParamType - универсальная ссылка
#опытным
Один и самых интересных, сложных, непонятных и противоречивых кейсов в выводе шаблонных параметров. Да и интересный он скорее из-за всего остального.
Только при такой сигнатуре шаблонной функции можно считать ее параметр универсальной ссылкой:
То есть это rvalue reference на cv-неквалифицированный тип. Только в таком виде тип param называется универсальной ссылкой. Как говорят в школе:
И ни в каком другом виде!
Ни
Это просто rvalue reference.
Ни
Обратите внимание на первые 3 кейса. Там Т выводится в lvalue reference тип. В двух последних Т - просто int безо всяких ссылок.
Мы на самом деле уже обсуждали универсальные ссылки в рамках серии статей про категории выражения. Вот ссылочка на эту статью с более полным описанием процессов.
В этой статье я просто хотел подсветить самые важные моменты в этой теме, которые касаются именно вывода типов.
Stay universal. Stay cool.
#cppcore #cpp11 #template
#опытным
Один и самых интересных, сложных, непонятных и противоречивых кейсов в выводе шаблонных параметров. Да и интересный он скорее из-за всего остального.
Только при такой сигнатуре шаблонной функции можно считать ее параметр универсальной ссылкой:
template <class T>
void func(T&& param) {...}
func(expression);
То есть это rvalue reference на cv-неквалифицированный тип. Только в таком виде тип param называется универсальной ссылкой. Как говорят в школе:
И ни в каком другом виде!
Ни
template <class T>
void func(std::vector<T>&& param) {...}
Это просто rvalue reference.
Ни
template <class T>
void func(const T&& param) {...}
Это тоже просто rvalue reference! Только константный.
И к последним двум кейсам применяются правила
отсюда
и
отсюда.
Когда expression - rvalue reference, то Т выводится безссылочным типом, чтобы тип ParamType был rvalue reference of T. Если тип expression - lvalue, то Т выводится в тип lvalue reference. Самое интересное, что это единственный кейс, когда тип Т выводится в ссылку.
Есть такое правило, что & + && = &. То есть при использовании универсальной ссылки в параметре шаблонной функции при передаче туда lvalue|lvalue reference, этот параметр выводится в lvalue reference. Это происходит именно за счет того, что шаблонный тип выводится в тип lvalue reference. Условно: функция принимает Т && , T выводится в int&, подставляем Т в параметр функции и получаем int& &&. Но такого синтаксиса нет и 2 ссылки коллапсируют в одну левую ссылку int&.
template<typename T> void f(T&& param); // param is a universal reference
int x = 27;
const int cx = x;
const int& lrx = x;
int&& rrx = 42;
f(x); // x is lvalue, so T is int&, param's type is also int&
f(cx); // cx is lvalue, so T is const int&, param's type is also const int&
f(lrx); // lrx is lvalue, so T is const int&, param's type is also const int&
f(27); // 27 is prvalue, so T is int, param's type is therefore int&&
f(std::move(rrx)); // rrx is xvalue, so T is int, param's type is therefore int&&
Обратите внимание на первые 3 кейса. Там Т выводится в lvalue reference тип. В двух последних Т - просто int безо всяких ссылок.
Мы на самом деле уже обсуждали универсальные ссылки в рамках серии статей про категории выражения. Вот ссылочка на эту статью с более полным описанием процессов.
В этой статье я просто хотел подсветить самые важные моменты в этой теме, которые касаются именно вывода типов.
Stay universal. Stay cool.
#cppcore #cpp11 #template
👍10🔥8❤2
Квизы
Сейчас и завтра пойдет пачка опросов по теме вывода шаблонных типов, чтобы вы могли проявить свои знания и проверить их на практике. Идею предложил Антон в своем комменте. Много постов опросников - это конечно не онлайн тренажер, но зато просто и легко в реализации. И каждый сможет попробовать.
Не будет драконьих конструкций, только все то, что мы уже знаем и разбирали на канале.
Не буду использовать телеграммные квизы с ответами, мне кажется это менее интерактивным форматом. Через пару часиков скину скопом объяснения по каждому случаю
У меня к вам всего один вопрос.
Во что выведется тип Т?
#quiz
Сейчас и завтра пойдет пачка опросов по теме вывода шаблонных типов, чтобы вы могли проявить свои знания и проверить их на практике. Идею предложил Антон в своем комменте. Много постов опросников - это конечно не онлайн тренажер, но зато просто и легко в реализации. И каждый сможет попробовать.
Не будет драконьих конструкций, только все то, что мы уже знаем и разбирали на канале.
Не буду использовать телеграммные квизы с ответами, мне кажется это менее интерактивным форматом. Через пару часиков скину скопом объяснения по каждому случаю
У меня к вам всего один вопрос.
Во что выведется тип Т?
#quiz
❤8👍2🔥2
Во что будет выведен тип Т?
Anonymous Poll
53%
const int
8%
const int&
12%
int
9%
std::deque<const int>
17%
Будет ошибка компиляции
Во что выведется тип Т?
Anonymous Poll
10%
int
14%
std::shared_ptr<const int>
65%
const int
11%
Будет ошибка компиляции
Во что выведется тип Т?
Anonymous Poll
21%
int const * const
5%
int
29%
const int
18%
int const *
27%
Будет ошибка компиляции
❤7
Объяснение
Кратко пройдемся по каждому кейсу и разберемся, что к чему.
Здесь казалось бы по всем правилам и канонам тип Т должен выводится в const int. Но мы с вами уже знаем, что стандартные контейнеры не могут быть инстанцированы с константными типами, поэтому здесь будет ошибка компиляции.
В этом случае правильным ответом будет Т - const int. Убираем от типа ref_ptr константность и ссылочность, снимаем слой std::shared_ptr и остается const int. Довольно просто.
Все выглядит так, что param - универсальная ссылка. Однако, это не так. Универсальная ссылка имеет вид Т&&, но с оговоркой, что Т - шаблонный параметр функции/метода, а не класса. В этом случае func принимает просто rvalue reference, а мы передаем туда lvalue. Компилятор не сможет забиндить rvalue reference на lvalue и произойдет ошибка компиляции.
Для начала, пусть вас не беспокоит, что const стоит после int, но перед символом указателя. Так можно делать, это просто альтернативная нотация для объявления константных типов.
То что изначальная переменная
Указатель здесь копируется по значению, поэтому внешняя константность типа ptr отбрасывается при выводе типа. Снимает слой константности и получает const int.
Вот такой формат закрепления материала. Завтра в любом случае выйдет похожий квиз, но если хотите больше такого взаимодействия - ставьте шампусик.
Check your knowledge. Stay cool.
Кратко пройдемся по каждому кейсу и разберемся, что к чему.
template <class T>
void func(const std::deque<T>& param) {}
func(std::deque<const int>{});
Здесь казалось бы по всем правилам и канонам тип Т должен выводится в const int. Но мы с вами уже знаем, что стандартные контейнеры не могут быть инстанцированы с константными типами, поэтому здесь будет ошибка компиляции.
template <class T>
void func(const std::shared_ptr<T> & ptr) {}
std::shared_ptr<const int> ptr{};
const std::shared_ptr<const int>& ref_ptr = ptr;
func(ref_ptr);
В этом случае правильным ответом будет Т - const int. Убираем от типа ref_ptr константность и ссылочность, снимаем слой std::shared_ptr и остается const int. Довольно просто.
template <class T>
struct Class {
static void func(T&& param) {}
};
int a = 0;
Class<int>::func(a);
Все выглядит так, что param - универсальная ссылка. Однако, это не так. Универсальная ссылка имеет вид Т&&, но с оговоркой, что Т - шаблонный параметр функции/метода, а не класса. В этом случае func принимает просто rvalue reference, а мы передаем туда lvalue. Компилятор не сможет забиндить rvalue reference на lvalue и произойдет ошибка компиляции.
template <class T>
void func(T * ptr) {}
int a = 0;
int const * const ptr = &a;
func(ptr);
Для начала, пусть вас не беспокоит, что const стоит после int, но перед символом указателя. Так можно делать, это просто альтернативная нотация для объявления константных типов.
То что изначальная переменная
а имеет тип int нас не должно волновать, мы смотрим только на ptr. Указатель здесь копируется по значению, поэтому внешняя константность типа ptr отбрасывается при выводе типа. Снимает слой константности и получает const int.
Вот такой формат закрепления материала. Завтра в любом случае выйдет похожий квиз, но если хотите больше такого взаимодействия - ставьте шампусик.
Check your knowledge. Stay cool.
🍾63👍11🔥4❤3🤯3🤬1
Во что выведется тип Т?
Anonymous Poll
41%
int
12%
std::vector<int>
15%
std::vector<int>&
33%
Будет ошибка компиляции
Во что выведется тип Т?
Anonymous Poll
11%
std::vector<int>
9%
int
58%
std::initializer_list<int>
23%
Будет ошибка компиляции
Во что выведется тип Т?
Anonymous Poll
48%
int
11%
std::shared_ptr<int>
15%
const int
25%
Будет ошибка компиляции