std::to_array
#опытным
std::array - прекрасная бесплатная обертка над сишными массивами. Но с ними есть один нюанс. При определении объекта массива нужно либо передавать оба шаблонных параметра(тип и размер):
либо надеяться на CTAD и не передавать никаких параметров:
Если так сложились звезды, что у вас тип инициализатора совпадает с желаемым типом элементов массива, то вам прекрасно подойдет последний вариант.
И вот здесь всплавает тот самый нюанс.
Не всегда тип инициализатора совпадает с типом элемента массива. Например я инициализирую строковыми литералами, а в массиве храню string_view. То есть мне нужно явно указать первый шаблонный параметр. Но второй я указывать не хочу! Я не хочу отбирать у компилятора работу по автоматическому выводу размера массива по количеству аргументов, которое я передал. А то вдруг передам меньше чем нужно и получу автозаполнение нулями. Пишу:
и получаю ошибку компиляции. Раз уж начал указывать шаблонные параметры, изволь и указать все.
Мне конечно не жалко написать эту жалкую тройку, пальцы не сотрутся. Но зачем? Компилятор же может определить размер и это уменьшит количество потенциальных ошибок.
У проблемы есть решение и начиная с С++20 оно является стандартом. Это функция std::to_array:
Идея такая: передаем в функцию элементы будущего массива с синтаксисом std::initializer_list. Комилялятор это парсит в сишный массив и автоматически выводит его длину в шаблонном параметре N. А дальше с помощью шаблонной магии с вариадик шаблонами правильно раскрываем сишный массив в инициализацию std::array.
Применяется std::to_array так:
Да, пишем на пару символов больше, зато размер будет четко соблюдаться.
Automate your tools. Stay cool.
#cpp20
#опытным
std::array - прекрасная бесплатная обертка над сишными массивами. Но с ними есть один нюанс. При определении объекта массива нужно либо передавать оба шаблонных параметра(тип и размер):
std::array<int, 4> arr = {1, 2, 3, 4};либо надеяться на CTAD и не передавать никаких параметров:
std::array arr = {1, 2, 3, 4};Если так сложились звезды, что у вас тип инициализатора совпадает с желаемым типом элементов массива, то вам прекрасно подойдет последний вариант.
И вот здесь всплавает тот самый нюанс.
Не всегда тип инициализатора совпадает с типом элемента массива. Например я инициализирую строковыми литералами, а в массиве храню string_view. То есть мне нужно явно указать первый шаблонный параметр. Но второй я указывать не хочу! Я не хочу отбирать у компилятора работу по автоматическому выводу размера массива по количеству аргументов, которое я передал. А то вдруг передам меньше чем нужно и получу автозаполнение нулями. Пишу:
std::array<std::string_view> arr = {"La", "Bu", "Bu"};и получаю ошибку компиляции. Раз уж начал указывать шаблонные параметры, изволь и указать все.
Мне конечно не жалко написать эту жалкую тройку, пальцы не сотрутся. Но зачем? Компилятор же может определить размер и это уменьшит количество потенциальных ошибок.
У проблемы есть решение и начиная с С++20 оно является стандартом. Это функция std::to_array:
namespace detail {
template <class T, std::size_t N, std::size_t... I>
constexpr std::array<std::remove_cv_t<T>, N>
to_array_impl(T (&a)[N], std::index_sequence<I...>) {
return {{a[I]...}};
}
} // namespace detail
template <class T, std::size_t N>
constexpr std::array<std::remove_cv_t<T>, N> to_array(T (&a)[N]) {
return detail::to_array_impl(a, std::make_index_sequence<N>{});
}Идея такая: передаем в функцию элементы будущего массива с синтаксисом std::initializer_list. Комилялятор это парсит в сишный массив и автоматически выводит его длину в шаблонном параметре N. А дальше с помощью шаблонной магии с вариадик шаблонами правильно раскрываем сишный массив в инициализацию std::array.
Применяется std::to_array так:
constexpr auto names = std::to_array<std::string_view>({"Goku", "Luffy", "Ichigo", "Gojo", "Joseph", "L"});Да, пишем на пару символов больше, зато размер будет четко соблюдаться.
Automate your tools. Stay cool.
#cpp20
❤26👍15🔥9🥱2☃1
WAT
#опытным
Спасибо, ₿ Satoshic, за любезно предоставленный примерчик в рамках рубрики #ЧЗХ.
Можно ли сравнить одинаковые объекты и получить результат, что они не равны? В С++ можно все.
Делаем вот так:
Определяем массив строк и в начале ищем в нем элемент, значение которого известно на момент компиляции.
Дальше определяем переменную окружения RUNTIME со значением третьего элемента массива.
После получаем значение этой переменной и сравниваем ее с оригиналом.
Ну и в конце ищем среди массива строку эту runtime_str.
Казалось бы, никакие ассерты не должны выстрелить. Мы просто занимается типичной программерской работой - перекладываем одно и то же значение в разные места и сравниваем. Одинаковые объекты должны быть равны.
Но нет! Не равны. Программа зафейлится с ассертом "Assertion `false && "runtime arg"' failed."
WAT?! Почему мы не можем найти строку "С++" в массиве, если она там очевидно есть?
Дьявол кроется в деталях.
Вспоминаемшкольную математеку CTAD. Какой тип элементов массива выведется?
Правильно, const char *.
А как std::ranges::find сравнивает такие элементы?
Правильно, по правилам сравнения указателей. Не по содержимому объектов, а по их адресам. Если адреса одинаковые, то два указателя равны. Нет - не равны.
Первый ассерт не сработал, потому что в массиве и при поиске стоит один и тот же строковый литерал "C++", на место которого компилятор подставит один и тот же адрес.
Второй ассерт не срабатывает, потому что мы явно сравниваем сишные строки через strcmp, то есть их содержимое.
А вот последний ассерт просто говорит о том, что указатель
И это нормально, ведь когда мы получаем указатель на значение переменной окружения - этот указатель указывает на динамически выделенную память в окружении процесса. А литерал "С++" указывает на секцию read-only данных.
В общем, суть в том, что эти указатели имеют просто разные адреса, поэтому они и не одинаковы.
Так что аккуратно используйте CTAD с сишными строками, может привести к интереснейшему каскаду удивительнейших багов.
Express your wishes precisely. Stay cool.
#cppcore #cpp17
#опытным
Спасибо, ₿ Satoshic, за любезно предоставленный примерчик в рамках рубрики #ЧЗХ.
Можно ли сравнить одинаковые объекты и получить результат, что они не равны? В С++ можно все.
Делаем вот так:
constexpr std::array array = {"I", "love", "C++"};
int main() {
if (auto iter = std::ranges::find(array, "C++"); iter == std::end(array)) {
assert(false && "comptime arg");
}
// let's go with runtime now
if (setenv("RUNTIME", array[2], 0) != 0) {
assert(false && "setenv");
}
char *runtime_str = getenv("RUNTIME");
assert(strcmp(runtime_str, array[2]) == 0 && "equal strings");
if (auto magick_iter = std::ranges::find(array, runtime_str);
magick_iter == std::end(array)) {
assert(false && "runtime arg");
}
}Определяем массив строк и в начале ищем в нем элемент, значение которого известно на момент компиляции.
Дальше определяем переменную окружения RUNTIME со значением третьего элемента массива.
После получаем значение этой переменной и сравниваем ее с оригиналом.
Ну и в конце ищем среди массива строку эту runtime_str.
Казалось бы, никакие ассерты не должны выстрелить. Мы просто занимается типичной программерской работой - перекладываем одно и то же значение в разные места и сравниваем. Одинаковые объекты должны быть равны.
Но нет! Не равны. Программа зафейлится с ассертом "Assertion `false && "runtime arg"' failed."
WAT?! Почему мы не можем найти строку "С++" в массиве, если она там очевидно есть?
Дьявол кроется в деталях.
Вспоминаем
Правильно, const char *.
А как std::ranges::find сравнивает такие элементы?
Правильно, по правилам сравнения указателей. Не по содержимому объектов, а по их адресам. Если адреса одинаковые, то два указателя равны. Нет - не равны.
Первый ассерт не сработал, потому что в массиве и при поиске стоит один и тот же строковый литерал "C++", на место которого компилятор подставит один и тот же адрес.
Второй ассерт не срабатывает, потому что мы явно сравниваем сишные строки через strcmp, то есть их содержимое.
А вот последний ассерт просто говорит о том, что указатель
runtime_str не был найден в массиве, потому что там нет такого адреса.И это нормально, ведь когда мы получаем указатель на значение переменной окружения - этот указатель указывает на динамически выделенную память в окружении процесса. А литерал "С++" указывает на секцию read-only данных.
В общем, суть в том, что эти указатели имеют просто разные адреса, поэтому они и не одинаковы.
Так что аккуратно используйте CTAD с сишными строками, может привести к интереснейшему каскаду удивительнейших багов.
Express your wishes precisely. Stay cool.
#cppcore #cpp17
❤21👍10😁7🔥5👎1
Как получить длину строкового литерала?
#опытным
Казалось бы, довольно простой вопрос. Обернем в строку и вызовем метод size:
Ну или на худой конец вызовем strlen:
Но я считаю, это не по-современному.
С++ давно идет в сторону расширения возможностей вычислений в compile-time. Поэтому если что-то можно вычислить во время компиляции, то это нужно сделать именно там! Ни грамма лишнего времени вычислений не потратим.
Давайте посмотрим, как можно найти длину строкового литерала во время компиляции.
1️⃣ Кастомщина. Хочешь что-то сделать хорошо, сделай это сам. Не факт, что получится хорошо, но ты старался:
Реальный тип строкового литерала не const char *, а константный массив символов. Поэтому через шаблон мы можем подтянуть размер массива через NTTP-параметр шаблона и вернуть его наружу.
2️⃣ Используем sizeof. Этот оператор возвращает длину массива во время компиляции. Единственное, что он считает терминирующий символ, поэтому все равно вокруг него надо обертку писать, чтобы единичку нигде не потерять:
Эх, а так хотелось готового get-to-go решения. Погодите...
3️⃣ Обернуть не в строку, а в string_view и вызвать метод size(). Конструкторы вьюхи изначально с С++17 были constexpr, как и сам метод size(), поэтому просто берем и пишем:
Просто, работает из коробки и знакомо всем.
4️⃣ Да зачем что-то менять в коде, это для слабаков. Поменяем стандарт и все заработает в compile-time! Ну точнее конструктор std::string и метод size() в С++20 теперь тоже constexpr:
Пысы: я не просто вызываю какие-то функции в надежде, что они выполнятся в compile-time. Тот факт, что len - constexpr переменная, требует, чтобы компилятор вычислил выражение справа во время компиляции.
5️⃣ Тот пункт, который и вдохновил на написание этого поста. Все пункты выше либо надо было самим реализовывать, либо вот какие-то обертки, чтобы хакнуть систему и на самом деле не работать с литералами.
Но не так плюсы бедны на стандартные решения. Есть стандартная С++17 функция std::char_traits<char>::length. Она может работать в compile-time, имеет явную семантику вычисления длины и работает чисто с c-style строками:
Красиво? Ну а что вы от плюсов хотели?) Зато из коробки работает.
6️⃣ Пользовательские литералы. Еще один неординарный способ. С С++11 мы имеем возможность превращать численные и строковые литералы в пользовательские объекты с помощью дописывания суффикса. Прикольно же писать:
Коротко и понятно. Для этого нужно лишь определить оператор преобразования:
и теперь вы свободны от угнетения оберток.
Если есть еще идеи, кидайте в комменты, будет интересно.
Don't be oppressed. Stay cool.
#cpp11 #cpp17 #cpp20
#опытным
Казалось бы, довольно простой вопрос. Обернем в строку и вызовем метод size:
size_t length = std::string("Hello, subscribers!").size();Ну или на худой конец вызовем strlen:
size_t length = strlen("Hello, subscribers!");Но я считаю, это не по-современному.
С++ давно идет в сторону расширения возможностей вычислений в compile-time. Поэтому если что-то можно вычислить во время компиляции, то это нужно сделать именно там! Ни грамма лишнего времени вычислений не потратим.
Давайте посмотрим, как можно найти длину строкового литерала во время компиляции.
1️⃣ Кастомщина. Хочешь что-то сделать хорошо, сделай это сам. Не факт, что получится хорошо, но ты старался:
template<size_t N>
constexpr size_t string_length(const char (&str)[N]) {
return N - 1; // do not count null terminator
}
constexpr size_t len = string_length("Hello, subscribers!");
Реальный тип строкового литерала не const char *, а константный массив символов. Поэтому через шаблон мы можем подтянуть размер массива через NTTP-параметр шаблона и вернуть его наружу.
2️⃣ Используем sizeof. Этот оператор возвращает длину массива во время компиляции. Единственное, что он считает терминирующий символ, поэтому все равно вокруг него надо обертку писать, чтобы единичку нигде не потерять:
template<size_t N>
constexpr size_t string_length(const char (&str)[N]) {
return sizeof(str) - 1; // do not count null terminator
}
constexpr size_t len = string_length("Hello, subscribers!");
Эх, а так хотелось готового get-to-go решения. Погодите...
3️⃣ Обернуть не в строку, а в string_view и вызвать метод size(). Конструкторы вьюхи изначально с С++17 были constexpr, как и сам метод size(), поэтому просто берем и пишем:
constexpr size_t len = std::string_view("Hello, subscribers!").size();Просто, работает из коробки и знакомо всем.
4️⃣ Да зачем что-то менять в коде, это для слабаков. Поменяем стандарт и все заработает в compile-time! Ну точнее конструктор std::string и метод size() в С++20 теперь тоже constexpr:
constexpr size_t len = std::string("Hello, subscribers!").size();Пысы: я не просто вызываю какие-то функции в надежде, что они выполнятся в compile-time. Тот факт, что len - constexpr переменная, требует, чтобы компилятор вычислил выражение справа во время компиляции.
5️⃣ Тот пункт, который и вдохновил на написание этого поста. Все пункты выше либо надо было самим реализовывать, либо вот какие-то обертки, чтобы хакнуть систему и на самом деле не работать с литералами.
Но не так плюсы бедны на стандартные решения. Есть стандартная С++17 функция std::char_traits<char>::length. Она может работать в compile-time, имеет явную семантику вычисления длины и работает чисто с c-style строками:
constexpr size_t len = std::char_traits<char>::length("Hello, subscribers!");Красиво? Ну а что вы от плюсов хотели?) Зато из коробки работает.
6️⃣ Пользовательские литералы. Еще один неординарный способ. С С++11 мы имеем возможность превращать численные и строковые литералы в пользовательские объекты с помощью дописывания суффикса. Прикольно же писать:
constexpr auto length = "Hello, subscribers!"_len;
Коротко и понятно. Для этого нужно лишь определить оператор преобразования:
constexpr size_t operator"" _len(const char* str, size_t n) {
return n;
}и теперь вы свободны от угнетения оберток.
Если есть еще идеи, кидайте в комменты, будет интересно.
Don't be oppressed. Stay cool.
#cpp11 #cpp17 #cpp20
🔥43👍16❤8
Пользовательские литералы
#новичкам
Последние несколько постов прям намекали, чтобы мы рассказали про пользовательские литералы(да и в комментах о них много говорили), поэтому here we are.
В наследство от С плюсам достались тривиальные типы и их литералы. Литералы - это способ записать готовое значение типа в коде. Литералы бывают:
👉🏿 Целочисленные:
👉🏿 С плавающей точкой:
👉🏿 Символьные: '
👉🏿 Строковые:
👉🏿 Логические:
👉🏿 Мало кто про это знает, но есть еще и литерал типа указателя -
Литералы также имеют фиксированный набор суффиксов, которые определяют их итоговый тип. Например, суффикс 'u' или 'U' для беззнакового целого, 'l' или 'L' для long, 'll' или 'LL' для long long, 'f' или 'F' для float. Суффикс также является полноправной частью литерала.
Прекрасная история, но эта история про тривиальные базовые типы. Никаких объектов.
А мы живем все-таки в мире объектов. И на стыке мира объектов и литералов тривиальных типов могу возникать конфузы, как в последнем WAT'е.
Но смотрите, что мы имеем. Число 42 в зависимости от суффикса может представлять разный числовой тип. Базовый тип целочисленного литерала - int. Но приписав U, получим unsigned int и тд.
То есть в С++ давно был механизм, с помощью которого можно было изменять тип литерала через суффикс. Стоит лишь дать возможность программистам самостоятельно определять свои суффиксы, чтобы по-своему интерпретировать литерал.
Это и сделали с С++11. Теперь мы можем определять свои пользовательские литералы с помощью нового оператора определения суффикса!
Допустим, моя программа много работает с градусами температуры. Мне нужно уметь работать с кельвинами, цельсиями и фаренгейтами. Для единообразия и точности для температуры у меня будет один класс и мне надо его научить работать с разными единицами изменения. Я конечно могу оборачивать чиселки в промежуточные классы, чтобы различать разные системы, или постоянно использовать фабричные функции, типа Temperature::from_kelvin. Но это прям больно как-то. Вместо этого можно определить пользовательские литералы:
Обратите на форму operator"". Он может возвращать что угодно и принимать какой-то из базовых типов литералов. Операторы различаются суффиксами. Пользовательские суффиксы обязаны начинаться с подчеркивания, потому что суффиксы без подчеркивания зарезервированы для стандарта.
Просто посмотрите, насколько сократился код, уменьшилось количество скобок и увеличилась читаемость. Выглядит круто.
Это было небольшое интро, в следующий раз рассмотрим кейсы, когда пользовательские литералы могут принести реальную пользу.
Extend your capabilities. Stay cool.
#cppcore #cpp11
#новичкам
Последние несколько постов прям намекали, чтобы мы рассказали про пользовательские литералы(да и в комментах о них много говорили), поэтому here we are.
В наследство от С плюсам достались тривиальные типы и их литералы. Литералы - это способ записать готовое значение типа в коде. Литералы бывают:
👉🏿 Целочисленные:
5, 42, 0xFF.👉🏿 С плавающей точкой:
3.14, 6.02e23.👉🏿 Символьные: '
a', '\n'.👉🏿 Строковые:
"Hello, world!".👉🏿 Логические:
true, false.👉🏿 Мало кто про это знает, но есть еще и литерал типа указателя -
nullptr.Литералы также имеют фиксированный набор суффиксов, которые определяют их итоговый тип. Например, суффикс 'u' или 'U' для беззнакового целого, 'l' или 'L' для long, 'll' или 'LL' для long long, 'f' или 'F' для float. Суффикс также является полноправной частью литерала.
Прекрасная история, но эта история про тривиальные базовые типы. Никаких объектов.
А мы живем все-таки в мире объектов. И на стыке мира объектов и литералов тривиальных типов могу возникать конфузы, как в последнем WAT'е.
Но смотрите, что мы имеем. Число 42 в зависимости от суффикса может представлять разный числовой тип. Базовый тип целочисленного литерала - int. Но приписав U, получим unsigned int и тд.
То есть в С++ давно был механизм, с помощью которого можно было изменять тип литерала через суффикс. Стоит лишь дать возможность программистам самостоятельно определять свои суффиксы, чтобы по-своему интерпретировать литерал.
Это и сделали с С++11. Теперь мы можем определять свои пользовательские литералы с помощью нового оператора определения суффикса!
Допустим, моя программа много работает с градусами температуры. Мне нужно уметь работать с кельвинами, цельсиями и фаренгейтами. Для единообразия и точности для температуры у меня будет один класс и мне надо его научить работать с разными единицами изменения. Я конечно могу оборачивать чиселки в промежуточные классы, чтобы различать разные системы, или постоянно использовать фабричные функции, типа Temperature::from_kelvin. Но это прям больно как-то. Вместо этого можно определить пользовательские литералы:
class Temperature {
private:
double kelvin; // for precicion and consistency
explicit Temperature(double k) : kelvin(k) {
if (k < 0) {
throw std::invalid_argument("Temperature cannot be below zero");
}
}
public:
static Temperature FromKelvin(double k) {
return Temperature(k);
}
static Temperature FromCelsius(double c) {
return Temperature(c + 273.15);
}
static Temperature FromFahrenheit(double f) {
return Temperature((f - 32.0) * 5.0/9.0 + 273.15);
}
// a bit more member functions for making it works
};
Temperature operator"" _kelvin(long double value) {
return Temperature::FromKelvin(static_cast<double>(value));
}
Temperature operator"" _celsius(long double value) {
return Temperature::FromCelsius(static_cast<double>(value));
}
Temperature operator"" _fahrenheit(long double value) {
return Temperature::FromFahrenheit(static_cast<double>(value));
}
{
auto t1 = Temperature::FromKelvin(0);
auto t2 = Temperature::FromCelsius(25);
auto t3 = Temperature::FromFahrenheit(98.6);
auto avg_temp = (Temperature::FromKelvin(20) + Temperature::FromCelsius(30)) / 2.0;
}
{
auto t1 = 0._kelvin;
auto t2 = 25._celsius;
auto t3 = 98.6_fahrenheit;
auto avg_temp = (20_kelvin + 30_celsius) / 2.0;
}
Обратите на форму operator"". Он может возвращать что угодно и принимать какой-то из базовых типов литералов. Операторы различаются суффиксами. Пользовательские суффиксы обязаны начинаться с подчеркивания, потому что суффиксы без подчеркивания зарезервированы для стандарта.
Просто посмотрите, насколько сократился код, уменьшилось количество скобок и увеличилась читаемость. Выглядит круто.
Это было небольшое интро, в следующий раз рассмотрим кейсы, когда пользовательские литералы могут принести реальную пользу.
Extend your capabilities. Stay cool.
#cppcore #cpp11
❤24👍12🔥11🤯2🤷♂1🤪1
Пользовательские литералы. А зачем?
#опытным
В прошлый раз мы поговорили о том, что такое пользовательские литералы. Сегодня поговорим о плюшках, которые могут дать user defined literals.
Поехали:
🥨 Они позволяет ввести адекватные легкочитаемые преобразование литералов в объекты классов. Не оборачивать все в конструкторы классов с кучей неймспейсов впереди, а просто добавив короткий суффикс. Тут все зависит от прикладной области, но можно легко придумать что-то вот такое:
Меньше деталей, больше фокуса на происходящем.
🥨 Предотвращают сочетание несочетаемого. Иногда в коде сложно определиться с типами переменных, особенно при обильном использовании
Получится неожиданный результат, даже если функция работает верно.
Вот шобы такого не было, можно использовать соответствующие литералы:
🥨 Автоматический вывод типов может легкосломаться вывести не тот тип, который вы ожидаете, если вы работаете с сырыми литералами. Пользовательский литерал же сразу на месте конструирует нужный объект и компилятор будет правильно интерпретировать его тип.
Особое внимание касательно этого пункта стоит обратить на строковые литералы. Их в 100% случаев нужно оборачивать в string_view. А с пользовательскими литералами это дело несложное:
Дописываем в конце строкового литерала sv и вот у вас в руках вьюха на строку. И компилятор корректно определяет тип элемента массива как вьюху.
🥨 Если вы хотите передать вашу строку, как NTTP в шаблон и что-то посчитать с ней в компайл-тайме - удачи, дело это нетривиальное. Но с С++20 это можно сделать через прокси класс:
И тут уже открываются просторы для реальных компайл-тайм вычислений над строками. И в этом также могут помочь кастомные литералы. Для примера можете посмотреть на видео от think-cell, как они работают со строковыми user-defined litarals: жмак.
В общем, крутая штука и нужно пользоваться. Если у вас есть свои примеры, пишите в комментах, интересно будет посмотреть.
Be useful. Stay cool.
#cppcore #cpp11 #cpp20
#опытным
В прошлый раз мы поговорили о том, что такое пользовательские литералы. Сегодня поговорим о плюшках, которые могут дать user defined literals.
Поехали:
🥨 Они позволяет ввести адекватные легкочитаемые преобразование литералов в объекты классов. Не оборачивать все в конструкторы классов с кучей неймспейсов впереди, а просто добавив короткий суффикс. Тут все зависит от прикладной области, но можно легко придумать что-то вот такое:
auto color1 = Color::from_html("#FF8800");
auto color2 = "#FF8800"_color;Меньше деталей, больше фокуса на происходящем.
🥨 Предотвращают сочетание несочетаемого. Иногда в коде сложно определиться с типами переменных, особенно при обильном использовании
auto. Поэтому легко может произойти такая ситуация, что вы возьмете и будете совместно оперировать синтаксически одинаковыми типами, но на деле они будут обозначать разные вещи. Условно, будем складывать градусы и радианы:double quadrant = math_constants::Pi / 2;
SomeMathCalculation(quadrant + 30.); // 30 is arc degree
Получится неожиданный результат, даже если функция работает верно.
Вот шобы такого не было, можно использовать соответствующие литералы:
class Radian {...};
Radian operator ""_deg(long double d)
{
return Radian{d*M_PI/180};
}
SomeMathCalculation(radian + 30._deg); // OK
SomeMathCalculation(radian + 30.); // Compiler error
🥨 Автоматический вывод типов может легко
Особое внимание касательно этого пункта стоит обратить на строковые литералы. Их в 100% случаев нужно оборачивать в string_view. А с пользовательскими литералами это дело несложное:
using namespace std::literals::string_view_literals;
constexpr std::array array1 = {"I", "love", "C++"};
static_assert(std::is_same_v<typename std::decay_t<decltype(array1[0])>,
const char *>);
constexpr std::array array2 = {"I"sv, "love"sv, "C++"sv};
static_assert(std::is_same_v<typename std::decay_t<decltype(array2[0])>,
std::string_view>);
Дописываем в конце строкового литерала sv и вот у вас в руках вьюха на строку. И компилятор корректно определяет тип элемента массива как вьюху.
🥨 Если вы хотите передать вашу строку, как NTTP в шаблон и что-то посчитать с ней в компайл-тайме - удачи, дело это нетривиальное. Но с С++20 это можно сделать через прокси класс:
template<size_t N>
struct FixedString {
char data[N];
constexpr FixedString(const char (&str)[N]) {
std::copy_n(str, N, data);
}
constexpr const char c_str() const { return data; }
constexpr size_t size() const { return N - 1; }
};
template <FixedString str>
class Class {};
Class<"Hello World!"> cl;
И тут уже открываются просторы для реальных компайл-тайм вычислений над строками. И в этом также могут помочь кастомные литералы. Для примера можете посмотреть на видео от think-cell, как они работают со строковыми user-defined litarals: жмак.
В общем, крутая штука и нужно пользоваться. Если у вас есть свои примеры, пишите в комментах, интересно будет посмотреть.
Be useful. Stay cool.
#cppcore #cpp11 #cpp20
👍14❤9🔥7😁1
Привычно возводим в степень
Спасибо, @Ivaneo, за любезно предоставленную идею для поста.
Как же бесило в универе, что в С/С++ нет нормального оператора возведения в степень, приходится использовать библиотечную std::pow. В других же языках такое есть. Например в питоне и рубях это оператор
В С++ мы такого в общем виде получить, к сожалению, не можем. Но можем сделать даже лучше в очень определенном сценарии.
И в этом нам помогут пользовательские литералы. Смотрите сами:
Берем уникод символ двойки верхнего регистра и делаем его суффикстом пользовательского литерала. И получаем почти привычную работающую версию возведения в квадрат! Это конечно не совсем стандарт, но на основных компиляторах работает.
А если еще и суффикс убрать:
То будет вообще огонь! Прям как в школе учили.
Да, суффиксы без андерскора запрещено использовать, так как они зарезервированы для стандарта. Но тем не менее это работает с варнингами на gcc и msvc, но уже не собирается на кланге.
Забавный примерчик. Жаль, что это может работать только для литералов и не распространяется на все переменные.
Provide better solutions. Stay cool.
#fun
Спасибо, @Ivaneo, за любезно предоставленную идею для поста.
Как же бесило в универе, что в С/С++ нет нормального оператора возведения в степень, приходится использовать библиотечную std::pow. В других же языках такое есть. Например в питоне и рубях это оператор
**(2 ** 3), а в Lua и Julia - оператор ^(2 ^ 3).В С++ мы такого в общем виде получить, к сожалению, не можем. Но можем сделать даже лучше в очень определенном сценарии.
И в этом нам помогут пользовательские литералы. Смотрите сами:
long double operator ""_²(long double d)
{
return d * d;
}
int main()
{
auto d = 2.0_²;
std::cout << d << "\n";
}
// OUTPUT:
// 4
Берем уникод символ двойки верхнего регистра и делаем его суффикстом пользовательского литерала. И получаем почти привычную работающую версию возведения в квадрат! Это конечно не совсем стандарт, но на основных компиляторах работает.
А если еще и суффикс убрать:
long double operator ""²(long double d)
{
return d * d;
}
int main()
{
auto d = 2.0²;
std::cout << d << "\n";
}
То будет вообще огонь! Прям как в школе учили.
Да, суффиксы без андерскора запрещено использовать, так как они зарезервированы для стандарта. Но тем не менее это работает с варнингами на gcc и msvc, но уже не собирается на кланге.
Забавный примерчик. Жаль, что это может работать только для литералов и не распространяется на все переменные.
Provide better solutions. Stay cool.
#fun
❤24🤣22👍11🔥6😁3🗿1
Забавный факт про std::unordered_map
#опытным
std::unoredered_map обязана работать на базе хэш-таблицы, чтобы удовлетворить требованиям по асимптотической сложности ее операций.
А хэш-таблицы обязаны использовать какой-либо механизм разрешения коллизий, которые случаются, когда хэш для двух ключей получается одинаковым. Они могут быть разные: линейное пробирование, двойное хэширование, round robin hashing и тд. Стандарт обычно описывает только требования к контейнерам, не погружаясь в детали реализации. Но в случае std::unordered_map он четко зафиксировал использование метода бакетов, когда каждая ячейка таблицы хранит связный список элементов, у которых одинаковый ключ.
При обычном итерировани по неупорядоченной мапе мы используем всем знакомый range-based for и обычные итераторы(под капотом этого форика):
Но это не единственный способ итерироваться по мапе!
У нее есть пара перегрузок методов begin() и end(), который принимают индекс бакета. И они позволяют итерироваться четко внутри него:
Количество бакетов мы получаем через метод bucket_size и готово, мы получили альтернативную итерацию по контейнеру!
Вывод:
Пользы в этом немного, но может помочь, например, в отладке своей кастомном хэш-функции, чтобы добиться равномерного распределения.
Inspect your solutions. Stay cool.
#cpp11
#опытным
std::unoredered_map обязана работать на базе хэш-таблицы, чтобы удовлетворить требованиям по асимптотической сложности ее операций.
А хэш-таблицы обязаны использовать какой-либо механизм разрешения коллизий, которые случаются, когда хэш для двух ключей получается одинаковым. Они могут быть разные: линейное пробирование, двойное хэширование, round robin hashing и тд. Стандарт обычно описывает только требования к контейнерам, не погружаясь в детали реализации. Но в случае std::unordered_map он четко зафиксировал использование метода бакетов, когда каждая ячейка таблицы хранит связный список элементов, у которых одинаковый ключ.
При обычном итерировани по неупорядоченной мапе мы используем всем знакомый range-based for и обычные итераторы(под капотом этого форика):
std::unoredered_map<std::string, int> map = ...;
for (const auto& [key, value]: map) {
...
}
Но это не единственный способ итерироваться по мапе!
У нее есть пара перегрузок методов begin() и end(), который принимают индекс бакета. И они позволяют итерироваться четко внутри него:
local_iterator begin( size_type n );
local_iterator end( size_type n );
Количество бакетов мы получаем через метод bucket_size и готово, мы получили альтернативную итерацию по контейнеру!
std::unordered_map<std::string, int> word_count = {
{"AI", 5}, {"evil", 7}, {"banana", 3},
{"date", 2}, {"elderberry", 4}
};
// Iterate over backets
for (size_t i = 0; i < word_count.bucket_count(); ++i) {
std::cout << "Bucket " << i << " ("
<< word_count.bucket_size(i) << " elements): ";
// Iterate inside certain backet
for (auto it = word_count.begin(i); it != word_count.end(i); ++it) {
std::cout << "[" << it->first << ":" << it->second << "] ";
}
std::cout << std::endl;
}Вывод:
Bucket 0 (0 elements):
Bucket 1 (0 elements):
Bucket 2 (2 elements): [date:2] [evil:7]
Bucket 3 (0 elements):
Bucket 4 (0 elements):
Bucket 5 (2 elements): [elderberry:4] [banana:3]
Bucket 6 (0 elements):
Bucket 7 (0 elements):
Bucket 8 (0 elements):
Bucket 9 (0 elements):
Bucket 10 (0 elements):
Bucket 11 (1 elements): [AI:5]
Bucket 12 (0 elements):
Пользы в этом немного, но может помочь, например, в отладке своей кастомном хэш-функции, чтобы добиться равномерного распределения.
Inspect your solutions. Stay cool.
#cpp11
🔥40😁15❤8👍6🤯2❤🔥1
Стандартные пользовательские литералы. Строковые
#новичкам
Невзначай мы уже упоминали в предыдущих постах о существовании стандартных пользовательских литералов. Сегодня же плотнее о них поговорим и об их особенностях.
Первая особенность - для их использования не нужно подчеркивание впереди суффикса. Стандарт может позволить зарезервировать для себя такой формат, чтобы не было коллизий с нашими кастомными операторами. Ну и без underscore'а приятнее визуально.
Вторая особенность - нужно обязательно указывать
В остальном, это те же кастомные литералы, только для стандартных типов. Подразделяются они по базовому типу литерала, к которому приписывается суффикс.
Строковые кастомные литералы
Интересно, что для них операторы принимают 2 параметра: указатель и длину:
Длина здесь без учета null-terminator'а. Компилятор при вызове оператора сам подставляет размер.
Есть всего 2 стандартных оператора, преобразующих c-style строку в объекты:
1️⃣ std::string:
2️⃣ std::string_view:
Второй оператор вообще стоит применять примерно со всеми c-style строками в вашем проекте, чтобы они были обернуты в понятные объекты и можно было пользоваться адекватным интерфейсом.
У них у обоих есть одна особенность. Так как размер строки передается в оператор и этот размер потом используется для создания объекта, то есть некоторые отличия при создании объектов через конструктор и через оператор:
Во втором случае получилась строка длиннее, чем в первом. Почему?
Для
Он конструирует строку из c-style строки и не знает ее настоящий размер. Поэтому он считает null-terminator концом строки.
Для
Теперь конструктор знает реальную длину строки и аллоцирует столько памяти, сколько нужно, чтобы поместить весь литерал в строку.
Для обычных строк, типа "Hello, World!" разницы не будет. Но если вы используете какие-то бинарные данные, то разница существенна.
Остальные стандартные литералы не уместились в ограничения телеги, поэтому будет вторая часть.
See the difference. Stay cool.
#cpp11 #cpp17
#новичкам
Невзначай мы уже упоминали в предыдущих постах о существовании стандартных пользовательских литералов. Сегодня же плотнее о них поговорим и об их особенностях.
Первая особенность - для их использования не нужно подчеркивание впереди суффикса. Стандарт может позволить зарезервировать для себя такой формат, чтобы не было коллизий с нашими кастомными операторами. Ну и без underscore'а приятнее визуально.
Вторая особенность - нужно обязательно указывать
using namespace std::literals помимо включения нужных хэдэров. Кастомный оператор - это по сути обычная функция. И при вызове функции из какого-то пространства имен(а все стандартное лежит как минимум в неймспейсе std) мы должны перед именем функции указать это пространство. Но как вы это сделаете с оператором? Да никак. Поэтому явно нужно использовать в своем коде неймспейс. Он общий для всех стандартных операторов, но есть еще и подпространства под конкретные их группы.В остальном, это те же кастомные литералы, только для стандартных типов. Подразделяются они по базовому типу литерала, к которому приписывается суффикс.
Строковые кастомные литералы
Интересно, что для них операторы принимают 2 параметра: указатель и длину:
( const char*, std::size_t )
Длина здесь без учета null-terminator'а. Компилятор при вызове оператора сам подставляет размер.
Есть всего 2 стандартных оператора, преобразующих c-style строку в объекты:
1️⃣ std::string:
constexpr std::string operator""s(const char* str, std::size_t len);
using namespace std::literals;
auto str = "Hello, World!"s;
static_assert(std::is_same_v<typename std::decay_t<decltype(str)>,
std::string>);
2️⃣ std::string_view:
constexpr std::string_view
operator ""sv(const char* str, std::size_t len) noexcept;
using namespace std::literals;
auto str = "Hello, World!"sv;
static_assert(std::is_same_v<typename std::decay_t<decltype(str)>,
std::string_view>);
Второй оператор вообще стоит применять примерно со всеми c-style строками в вашем проекте, чтобы они были обернуты в понятные объекты и можно было пользоваться адекватным интерфейсом.
У них у обоих есть одна особенность. Так как размер строки передается в оператор и этот размер потом используется для создания объекта, то есть некоторые отличия при создании объектов через конструктор и через оператор:
void print_with_zeros(const auto note, const std::string& s) {
std::cout << note;
for (const char c : s)
c ? std::cout << c : std::cout << "₀";
std::cout << " (size = " << s.size() << ")\n";
}
int main() {
using namespace std::string_literals;
std::string s1 = "abc\0\0def";
std::string s2 = "abc\0\0def"s;
print_with_zeros("s1: ", s1);
print_with_zeros("s2: ", s2);
}
// OUTPUT:
// s1: abc (size = 3)
// s2: abc₀₀def (size = 8)Во втором случае получилась строка длиннее, чем в первом. Почему?
Для
s1 вызывается конструктор от одного аргумента:basic_string( const CharT* s, const Allocator& alloc = Allocator() );
Он конструирует строку из c-style строки и не знает ее настоящий размер. Поэтому он считает null-terminator концом строки.
Для
s2 вызывается конструктор от двух аргументов:basic_string( const CharT* s, size_type count,
const Allocator& alloc = Allocator() );
Теперь конструктор знает реальную длину строки и аллоцирует столько памяти, сколько нужно, чтобы поместить весь литерал в строку.
Для обычных строк, типа "Hello, World!" разницы не будет. Но если вы используете какие-то бинарные данные, то разница существенна.
Остальные стандартные литералы не уместились в ограничения телеги, поэтому будет вторая часть.
See the difference. Stay cool.
#cpp11 #cpp17
❤28🔥12👍8😁7🤔1
Стандартные пользовательские литералы. Числовые
#новичкам
Числа подходят для инициализации многих сущностей. Метры, килограммы, градусы и тд. В стандартной библиотеке не так уж и много классов, значения которых можно представить числами. Но тем не менее они есть и литералы становятся довольно полезными при использовании.
Наибольшее распространение числовые пользовательские литералы получили при работе со временем.
Мы все привыкли писать:
Вы также можете верхнюю шестерку операторов использовать совместно в операциях:
Они совместимы и в результате получается объект общего типа(какой конкретно зависит от реализации, но скорее всего std::chrono::seconds в данном случае)
В крутящемся на проде коде нечасто можно увидеть использование какого-то захардкоженного промежутка времени. Обычно такие штуки уносят в конфигурацию, чтобы иметь возможность подкрутить эти параметры без изменения кода.
Однако литералы времени отлично можно применить в тестах. Например, хотите вы протестировать свой многопоточный шедулер. В качестве простого теста можно запихать в него лямбду, в которой установить значение промиса. А снаружи явно ждать установки значения:
если по истечению 100ms у фьючи не будет статуса "готово", то тест падает.
Интересный факт: оператор суффикс
Ну и еще есть литералы для комплексных чисел:
Работает там примерно так же, как в математике.
Не уверен, что кто-то этим пользуется. Но если пользуетесь, расскажите над каким проектом работаете, интересно же.
Be useful. Stay cool.
#cpp14
#новичкам
Числа подходят для инициализации многих сущностей. Метры, килограммы, градусы и тд. В стандартной библиотеке не так уж и много классов, значения которых можно представить числами. Но тем не менее они есть и литералы становятся довольно полезными при использовании.
Наибольшее распространение числовые пользовательские литералы получили при работе со временем.
Мы все привыкли писать:
5ч или 34мин. И начиная с С++14 мы примерно так и можем оперировать временем. Есть операторы преобразования целых и дробных чисел в годы, дни, часы, минуты, секунды, миллисекунды, микросекунды и наносекунды:using namespace std::literals;
auto ns = 100ns; // наносекунды
auto us = 100us; // микросекунды
auto ms = 100ms; // миллисекунды
auto s = 100s; // секунды
auto min = 100min; // минуты
auto h = 24h; // часы
auto d = 42d; // дни
auto y = 12y; // года
Вы также можете верхнюю шестерку операторов использовать совместно в операциях:
auto time = 1h + 30min + 90s;
Они совместимы и в результате получается объект общего типа(какой конкретно зависит от реализации, но скорее всего std::chrono::seconds в данном случае)
В крутящемся на проде коде нечасто можно увидеть использование какого-то захардкоженного промежутка времени. Обычно такие штуки уносят в конфигурацию, чтобы иметь возможность подкрутить эти параметры без изменения кода.
Однако литералы времени отлично можно применить в тестах. Например, хотите вы протестировать свой многопоточный шедулер. В качестве простого теста можно запихать в него лямбду, в которой установить значение промиса. А снаружи явно ждать установки значения:
std::promise<void> promise;
auto future = promise.get_future();
dispatcher_->schedule([&] { promise.set_value(); });
EXPECT_EQ(future.wait_for(100ms), std::future_status::ready);
если по истечению 100ms у фьючи не будет статуса "готово", то тест падает.
Интересный факт: оператор суффикс
s конфликтует своим именем с оператором преобразования к строке. Но проблема решается автоматически разным типом аргументов. Никаких реальных конфликтов, обычная перегрузка:std::string_literals::operator"" s(const char*, size_t)
std::chrono_literals::operator"" s(unsigned long long)
Ну и еще есть литералы для комплексных чисел:
using namespace std::literals;
auto c1 = 1.0 + 2.0i; // std::complex<double>(1.0, 2.0)
auto c2 = 3.0i; // std::complex<double>(0.0, 3.0)
auto c3 = 4.0if; // std::complex<float>(0.0f, 4.0f)
auto c4 = 5.0il; // std::complex<long double>(0.0L, 5.0L)
Работает там примерно так же, как в математике.
Не уверен, что кто-то этим пользуется. Но если пользуетесь, расскажите над каким проектом работаете, интересно же.
Be useful. Stay cool.
#cpp14
❤25😁17🔥12👍3⚡1
Доступ к приватным членам. Макросы
#новичкам
Доступ к приватным членам? Фуфуфу, это грязь! Да как вы смеете?! Хорошие люди старались, инкапсуляцию изобретали, а вы надругаться над ними хотите? Не по-славянски это, не по-православному...
Не далеки от правды слова выше. Если у вас уже есть какой-то работающий класс и вы хотите ужом извернуться, чтобы вытащить его кишки наружу - надо задуматься. О степени своей маниакальности, но главное - над архитектурой вашего кода. Потому что в большинстве случаев вы будете делать какое-то безобразие и разного рода хаки, чтобы подсмотреть в приватные поля. Лучше чуть подольше подумать и переработать целиком решение с учетом новых вводных.
Но!
Врага надо знать в лицо!
Поэтому в течение нескольких следующих постов мы будем обсуждать варианты инвазивного и неинвазивного доступа к приватным членам класса. Как говорится: не повторяйте в проде, чревато говнокодом по теории разбитых окон.
И на завтрак мы разберем самый простой способ. Макросы.
Есть у нас хэдэр:
Подключаем его в цппшник, но перед этим делаем грязь:
Строчкой
И это работает! Да, стандартом запрещено заменять макросами ключевые слова. Но это вообще не волнует компиляторы. gcc даже c флагами -pedantic -Wall не выдает никаких предупреждений. clang только с флагом -pedantic генерирует варнинг.
Конечно же за такой макрос надо не то что по рукам бить. Надо их из жопы вырывать без анастезии и вставлять в нормальное место.
Пожалуй, это самый дурнопахнущих из всех способов, потому что ломает инкапсуляцию прям везде. Так сказать начали с вкуснятины. Но оставайтесь на свзяи, продолжение тоже будет вкусным.
Be legal. Stay cool.
#NONSTANDARD #badpractice
#новичкам
Доступ к приватным членам? Фуфуфу, это грязь! Да как вы смеете?! Хорошие люди старались, инкапсуляцию изобретали, а вы надругаться над ними хотите? Не по-славянски это, не по-православному...
Не далеки от правды слова выше. Если у вас уже есть какой-то работающий класс и вы хотите ужом извернуться, чтобы вытащить его кишки наружу - надо задуматься. О степени своей маниакальности, но главное - над архитектурой вашего кода. Потому что в большинстве случаев вы будете делать какое-то безобразие и разного рода хаки, чтобы подсмотреть в приватные поля. Лучше чуть подольше подумать и переработать целиком решение с учетом новых вводных.
Но!
Врага надо знать в лицо!
Поэтому в течение нескольких следующих постов мы будем обсуждать варианты инвазивного и неинвазивного доступа к приватным членам класса. Как говорится: не повторяйте в проде, чревато говнокодом по теории разбитых окон.
И на завтрак мы разберем самый простой способ. Макросы.
Есть у нас хэдэр:
// header.cpp
#pragma once
class X {
public:
X() : private_(1) { /.../
}
template <class T>
void f(const T &t) { /.../
}
int Value() { return private_; }
// ...
private:
int private_;
};
Подключаем его в цппшник, но перед этим делаем грязь:
#define private public
#include "source.h"
#include <iostream>
void Hijack( X& x )
{
x.private_ = 2;
}
int main() {
X x;
Hijack(x);
std::cout << "Hi, there! Hack has performed successfully" << std::endl;
}
Строчкой
#define private public вы заменяете все нижележащие по коду вхождения слова private на public. Таким образом вы не трогаете хэдэр, но все равно имеете доступ к абсолютно всем его полям и методам.И это работает! Да, стандартом запрещено заменять макросами ключевые слова. Но это вообще не волнует компиляторы. gcc даже c флагами -pedantic -Wall не выдает никаких предупреждений. clang только с флагом -pedantic генерирует варнинг.
Конечно же за такой макрос надо не то что по рукам бить. Надо их из жопы вырывать без анастезии и вставлять в нормальное место.
Пожалуй, это самый дурнопахнущих из всех способов, потому что ломает инкапсуляцию прям везде. Так сказать начали с вкуснятины. Но оставайтесь на свзяи, продолжение тоже будет вкусным.
Be legal. Stay cool.
#NONSTANDARD #badpractice
❤35👍11😁10😭6🔥5🤯5😱4🤣1
Доступ к приватным членам. Указатали
#новичкам
Если по-честному, то все эти спецификаторы доступа к членам класса, это чисто синтаксическое ограничение на непреднамеренное использование в коде имен непубличных членов. Ну и способ выделение в классе интерфейса, чтобы сказать другим программистам, какими легальными способами можно оперировать объектом.
Но если вы хотите непотребств, вас никто не может ограничить. С++ имеет прямой доступ к памяти, а значит вы можете посмотреть под лупой, понюхать и облизать любой байтик объекта.
То есть банально
Как бы логично предположить, что если у класса только одно поле, то сам объект будет состоять только из этого поля. И можно спокойно привести указатель на объект к указателю на поле.
Но здесь есть целых 2 проблемы.
1️⃣ Нарушение strict aliasing. Мы интерпретируем указатель на объект, как указатель на другой тип. Это UB по стандарту. Это значит, что ваше решение непереносимо и результат может отличаться в зависимости от компилятора и опций компиляции.
2️⃣ Вторая еще серьезнее. Цитата из стандарта:
Если ваша кодовая конструкция интерпретируется, как использование недоступных вам мемберов, то конструкция ill-formed. Не сказано, что сама программа ifndr, но это все равно значит, что код выше не соответствует правилам языка.
Первую проблему можно обойти с помощьюдыры в стандарте memcpy:
Но вторую проблему никак не убрать. Если вы получаете доступ к недоступным вам в текущем контексте полям через такие низкоуровневые инструменты, ваш код is dog shit.
Если мемберов много, то нужно будет учитывать выравнивание полей в объекте.
В общем, это все может работать на конкретной архитектуре и компиляторе, если вы сами все руками в каждом конкретном случае проверяете. Но стандарт вас осуждает и ничего не обещает.
Пусть в конце каждого поста из серии будет эпилог: получать доступ к приватным полям - плохо! Мы с вами это делаем для понимания механик языка, а не для вооружения здешних обителей оружием массового закакивания кода.
Be legal. Stay cool.
#cppcore #badpractice
#новичкам
Если по-честному, то все эти спецификаторы доступа к членам класса, это чисто синтаксическое ограничение на непреднамеренное использование в коде имен непубличных членов. Ну и способ выделение в классе интерфейса, чтобы сказать другим программистам, какими легальными способами можно оперировать объектом.
Но если вы хотите непотребств, вас никто не может ограничить. С++ имеет прямой доступ к памяти, а значит вы можете посмотреть под лупой, понюхать и облизать любой байтик объекта.
То есть банально
class MyClass {
private:
int secret = 42;
};
void illegalAccess(MyClass &obj) {
int *ptr = (int *)&obj; // assume that secret is first member
std::cout << "Illegal: " << *ptr << std::endl;
}Как бы логично предположить, что если у класса только одно поле, то сам объект будет состоять только из этого поля. И можно спокойно привести указатель на объект к указателю на поле.
Но здесь есть целых 2 проблемы.
1️⃣ Нарушение strict aliasing. Мы интерпретируем указатель на объект, как указатель на другой тип. Это UB по стандарту. Это значит, что ваше решение непереносимо и результат может отличаться в зависимости от компилятора и опций компиляции.
2️⃣ Вторая еще серьезнее. Цитата из стандарта:
The interpretation of a given construct is established without regard to access control. If the interpretation established makes use of inaccessible members or base classes, the construct is ill-formed. Если ваша кодовая конструкция интерпретируется, как использование недоступных вам мемберов, то конструкция ill-formed. Не сказано, что сама программа ifndr, но это все равно значит, что код выше не соответствует правилам языка.
Первую проблему можно обойти с помощью
void AccessWithMemcpy(MyClass& obj) {
int value;
std::memcpy(&value, &obj, sizeof(int));
std::cout << value << std::endl;
}Но вторую проблему никак не убрать. Если вы получаете доступ к недоступным вам в текущем контексте полям через такие низкоуровневые инструменты, ваш код is dog shit.
Если мемберов много, то нужно будет учитывать выравнивание полей в объекте.
В общем, это все может работать на конкретной архитектуре и компиляторе, если вы сами все руками в каждом конкретном случае проверяете. Но стандарт вас осуждает и ничего не обещает.
Пусть в конце каждого поста из серии будет эпилог: получать доступ к приватным полям - плохо! Мы с вами это делаем для понимания механик языка, а не для вооружения здешних обителей оружием массового закакивания кода.
Be legal. Stay cool.
#cppcore #badpractice
❤21👍17😁9🔥6❤🔥1😭1
Квиз
#новичкам
Буквально на секундочку вернемся к теме пользовательских литералов.
Со строковыми литералами всегда какая-то беда происходит. То тип путает карты, то этот null-terminator комом в горле встает, то чтобы вычислить длину надо быть кмс по приседаниям.
Но в комбинации с пользовательскими операторами может получиться такая кракозябра, что фиг разберешь.
А разбирать надо для понимания процессов. Поэтому сегодня проверим ваши интуицию/знания в рамках небольшого #quiz 'а.
Какой результат попытки компиляции и запуска кода ниже под С++23?
#новичкам
Буквально на секундочку вернемся к теме пользовательских литералов.
Со строковыми литералами всегда какая-то беда происходит. То тип путает карты, то этот null-terminator комом в горле встает, то чтобы вычислить длину надо быть кмс по приседаниям.
Но в комбинации с пользовательскими операторами может получиться такая кракозябра, что фиг разберешь.
А разбирать надо для понимания процессов. Поэтому сегодня проверим ваши интуицию/знания в рамках небольшого #quiz 'а.
Какой результат попытки компиляции и запуска кода ниже под С++23?
#include <iostream>
#include <iomanip>
#include <type_traits>
#include <cmath>
int operator ""_length(const char*, std::size_t length) { return length; }
int main()
{
auto s = "A" "B"_length "C"
"D"
"E"
"FGH";
std::cout << s << "\n";
}
🤔10👍7❤4🔥2🤯1
Какой результат попытки компиляции и запуска кода под С++23?
Anonymous Poll
40%
Ошибка компиляции. Между двух строковых литералов не может стоять число.
18%
Ошибка компиляции. Подряд литералы вообще нельзя ставить, тем более с переносами.
13%
На экран выведется 2
4%
На экран выведется 3
20%
На экран выведется 8
5%
На экран выведется 9
👨💻Хотите начать карьеру в разработке? Обратите внимание на Rust и познакомьтесь с ним за один вечер!
📆На открытом уроке 25 февраля в 20:00 МСК вы установите инструменты, разберётесь с rustc и Cargo и создадите своё первое приложение. Пошагово, с объяснением каждой команды и структуры проекта.
Вы увидите, как Rust решает реальные проблемы C++, Python и других языков, где ошибки часто проявляются слишком поздно. Поймёте философию языка и получите готовую среду для дальнейшего развития. Если вы рассматриваете Rust как следующий шаг в карьере, этот урок — эффективная точка входа.
👉Встречаемся в преддверии старта курса «Rust Developer. Basic». Зарегистрируйтесь и начните системно разбираться в языке, который уже меняет индустрию: https://otus.pw/3b4Q/
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
📆На открытом уроке 25 февраля в 20:00 МСК вы установите инструменты, разберётесь с rustc и Cargo и создадите своё первое приложение. Пошагово, с объяснением каждой команды и структуры проекта.
Вы увидите, как Rust решает реальные проблемы C++, Python и других языков, где ошибки часто проявляются слишком поздно. Поймёте философию языка и получите готовую среду для дальнейшего развития. Если вы рассматриваете Rust как следующий шаг в карьере, этот урок — эффективная точка входа.
👉Встречаемся в преддверии старта курса «Rust Developer. Basic». Зарегистрируйтесь и начните системно разбираться в языке, который уже меняет индустрию: https://otus.pw/3b4Q/
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🤣21❤3👍2🔥2😁2😢1
WAT
#новичкам
Спасибо, @Ivaneo, за любезно предоставленный примерчик в рамках рубрики #ЧЗХ.
Ответ на квиз из поста выше - на экран выведется 8.
WAT? Строковые литералы конкатенируются? Да еще и пользовательский суффикс между двух литералов применяется к конкатенации?
Вообще, да. Сейчас во всем разберемся.
Для начала. Да, c-style строки конкатенируются(склеиваются). И это бывает очень полезно, особенно при работе с длинными строками.
С длинными строками, которые целиком не влезают на экран, неудобно работать: читать и редактировать. А если у вас настроены линтеры на ограничение длины строки, то все равно придется как-то разбивать на части эту длинную строку.
Можно это делать с помощью символов экранирования, например так:
Но здесь будут проблемы с тем, что вторая часть должна начинаться с самого начала следующей строки, иначе пробелы будут включены в сам литерал. Представьте, что будет с кодом, в котором внутри функций(например для репорта ошибок) будут так разделяться литералы.
Чтобы этих проблем не было, существует конкатенация строковых литералов. Буквально:
Не важно сколько пробелов или новых строчек находится между подряд идущими литералами. Они все объединятся при компиляции. Можно даже комменты между ними ставить, они все равно склеятся.
Ну и теперь понятно, почему пользовательский суффикс применяется к полной конкатенации c-style строки. Фаза конкатенации строковых литералов идет раньше этапа компиляции, на котором определяется значение аргументов оператора. Поэтому аргументом и является уже склеенная строка.
Однако разрешается только один пользовательский суффикс использовать. Два и больше - ошибка компиляции.
Кстати, такая склейка есть только у строковых литералов. Цифры в числовых литералах обязательно должны идти подряд:
Если вы хотите как-то сгруппировать цифры в числе, то можете использовать бинарные литералы(вот этот штрих в num3).
Don't break into pieces. Be whole. Stay cool.
#cppcore #cpp11
#новичкам
Спасибо, @Ivaneo, за любезно предоставленный примерчик в рамках рубрики #ЧЗХ.
Ответ на квиз из поста выше - на экран выведется 8.
WAT? Строковые литералы конкатенируются? Да еще и пользовательский суффикс между двух литералов применяется к конкатенации?
Вообще, да. Сейчас во всем разберемся.
Для начала. Да, c-style строки конкатенируются(склеиваются). И это бывает очень полезно, особенно при работе с длинными строками.
С длинными строками, которые целиком не влезают на экран, неудобно работать: читать и редактировать. А если у вас настроены линтеры на ограничение длины строки, то все равно придется как-то разбивать на части эту длинную строку.
Можно это делать с помощью символов экранирования, например так:
auto str = "Suuuuuuuuuuuuuuupppeeeeeeeeeeeeeeeeeeeeeeeeeeerrrr
loooooooooooooooooooooooooooooong \ striiiiiiiiiiiiiiiiiiiiiiiiiiiiiing";
Но здесь будут проблемы с тем, что вторая часть должна начинаться с самого начала следующей строки, иначе пробелы будут включены в сам литерал. Представьте, что будет с кодом, в котором внутри функций(например для репорта ошибок) будут так разделяться литералы.
Чтобы этих проблем не было, существует конкатенация строковых литералов. Буквально:
auto str = "Hello "
// void
"World!";
std::cout << str << std::endl;
// OUTPUT
// Hello World!
Не важно сколько пробелов или новых строчек находится между подряд идущими литералами. Они все объединятся при компиляции. Можно даже комменты между ними ставить, они все равно склеятся.
Ну и теперь понятно, почему пользовательский суффикс применяется к полной конкатенации c-style строки. Фаза конкатенации строковых литералов идет раньше этапа компиляции, на котором определяется значение аргументов оператора. Поэтому аргументом и является уже склеенная строка.
Однако разрешается только один пользовательский суффикс использовать. Два и больше - ошибка компиляции.
Кстати, такая склейка есть только у строковых литералов. Цифры в числовых литералах обязательно должны идти подряд:
int num1 = 123; // OK
int num2 = 12 23 // ERROR
int num3 = 1'234; // if you want to logicaly devide large number
Если вы хотите как-то сгруппировать цифры в числе, то можете использовать бинарные литералы(вот этот штрих в num3).
Don't break into pieces. Be whole. Stay cool.
#cppcore #cpp11
🔥18👍6❤4🤯4❤🔥1