Пример работает потому, что в момент итерации по контейнеру std::map
мы получаем узлы std::pair
на каждом шаге этого процесса. Именно эти узлы распаковываются с помощью структурированных привязок (key_type
представляет собой строку с именем species
, а value_type
— переменную count типа size_t
), что позволяет получить к ним доступ по отдельности в теле цикла.
До появления C++17 аналогичного эффекта можно было достичь с помощью std::tie:
int remainder;
std::tie(std::ignore, remainder) = divide_remainder(16, 5);
std::cout << "16 % 5 is " << remainder << '\n';
Здесь показано, как распаковать полученную пару в две переменные. Применение контейнера std::tie
не так удобно, как использование декомпозиции, ведь нам надо std::tie
std::ignore
играет роль переменной-пустышки. В данном случае частное нас не интересует и мы отбрасываем его, связав с std::ignore
.
tie
, поэтому нужно привязывать все значения к именованным переменным. Это может оказаться неэффективным, если позже не задействовать некоторые переменные, но тем не менее компилятор может оптимизировать неиспользованное связывание.
Раньше функцию divide_remainder
можно было реализовать следующим образом, используя выходные параметры:
bool divide_remainder(int dividend, int divisor,
int &fraction, int &remainder);
Получить к ним доступ можно так:
int fraction, remainder;
const bool success {divide_remainder(16, 3, fraction, remainder)};
if (success) {
std::cout << "16/3 is " << fraction << " with a remainder of "
<< remainder << '\n';
}
Многие все еще предпочитают делать именно так, а не возвращать пары, кортежи и структуры. При этом они приводят следующие аргументы: код работает
return value optimization
, RVO), что позволяет избежать создания промежуточных копий.
Ограничиваем область видимости переменных в выражениях if и switch
Максимальное ограничение области видимости переменных считается хорошим тоном. Иногда, однако, переменная должна получить какое-то значение, а потом нужно его проверить на соответствие тому или иному условию, чтобы продолжить выполнение программы. Для этих целей в С++17 была введена инициализация переменных в выражениях if
и switch
.
Как это делается
В данном примере мы воспользуемся новым синтаксисом в обоих контекстах, чтобы увидеть, насколько это улучшит код.
□ Выражение if
. Допустим, нужно найти символ в таблице символов с помощью метода find
контейнера std::map
:
if (auto itr (character_map.find(c));
itr != character_map.end()) {
// *itr корректен. Сделаем с ним что-нибудь.
} else {
// itr является конечным итератором. Не разыменовываем.
}
// здесь itr недоступен
□ Выражение switch
. Так выглядит код получения символа из пользовательского ввода и его одновременная проверка в выражении switch
для дальнейшего управления персонажем компьютерной игры:
switch (char c (getchar()); c) {
case 'a': move_left(); break;
case 's': move_back(); break;
case 'w': move_fwd(); break;
case 'd': move_right(); break;
case 'q': quit_game(); break;
case '0'...'9': select_tool('0' - c); break;
default:
std::cout << "invalid input: " << c << '\n';
}
Как это работает