Начиная с версии стандарта C++17 в стандартную библиотеку C++ был добавлен тип std::optional<T> (модуль optional), который позволяет избежать таких ситуаций, когда значение не найдено или не устнавлено, и определить для подобных ситуаций значение по умолчанию. Рассмотрим конкретную ситуацию, для чего он нужен.
Например, нам надо определить функцию для поиска индекса символа в строке. С одной стороны, все просто - перебираем строку посимвольно, находим нужный символ и возвращаем его индекс. С другой стороны, а что, если символ не будет найден? На этот случай мы могли бы предусмотреть возвращение некоторого значения по умолчанию. Наиболее часто используемым вариантом в данном случае является число -1, поскольку данное число не представляет действительный индекс. И получив его из функции, мы будем знать, что символ не найден.
Тип optional предоставляет альтернативный подход: если индекс найден, то он возвращается. Если значение не найдено, то возвращается константа
std::nullopt, которая указывает, что значение optional
не установлено.
Для примера определим следующую программу:
#include <iostream> #include <string> #include <optional> std::optional<unsigned> find_index(const std::string&, char); int main() { const std::string text = "An apple a day keep the doctor away."; // находим индекс символа 'p' char p_char{'p'}; const std::optional<unsigned> p_index{ find_index(text, p_char) }; std::cout << "Index of p: " << *p_index << std::endl; // находим индекс символа 'b' char b_char{'b'}; const std::optional<unsigned> b_index{ find_index(text, b_char) }; std::cout << "Index of b: " << *b_index << std::endl; } std::optional<unsigned> find_index(const std::string& text, char c) { // если пустая строка, возвращаем специальное значение std::nullopt if (text.empty()) return std::nullopt; // в цикле находим начальный индекс символа for(unsigned i{}; i < text.size();i++) { // если символ найден, возвращаем индекс символа if(text[i]==c) { return i; } } // в остальных случаях возвращаем std::nullopt return std::nullopt; }
Здесь определена функция find_index()
, которая принимает text в виде константной ссылки на строку и символ для поиска и возвращает значение std::optional<unsigned>
.
Объект optional
типизируется типом значений, которые он должен содержать. Поскольку индекс символа представляет целое беззнаковое число, то здесь типизируем
optional
типом unsigned
. В самой функции, если символ не найден или строка пуста, то возвращаем значение std::nullopt
.
Таким образом, если индекс найден, то optional
будет содержать найденный индекс. А если индекс не найден, то значение std::nullopt
В функции main ищем два символа: "p", который есть в исходном тексте, и "b", который отсутствует. Для поления значения из option
можно использовать операцию
*, например, *p_index
. В итоге в данном случае мы получим следующий вывод:
Index of p: 4 Index of b: 4251975392
Так, мы видим, что поскольку символ "b" не найден, optional
будет содержать довольно большое число, которое и представляет std::nullopt
.
Пойдем дальше и изменим программу следующим образом:
#include <iostream> #include <string> #include <optional> std::optional<unsigned> find_index(const std::string&, char); void print_index(std::optional<unsigned>, char); int main() { const std::string text = "An apple a day keep the doctor away."; // находим индекс символа 'p' char p_char{'p'}; const std::optional<unsigned> p_index{ find_index(text, p_char) }; print_index(p_index, p_char); // находим индекс символа 'b' char b_char{'b'}; const std::optional<unsigned> b_index{ find_index(text, b_char) }; print_index(b_index, b_char); } // выводим индекс на консоль, если символ найден void print_index(std::optional<unsigned> index, char c) { if(index) std::cout << "Index of "<< c << ": " << *index << std::endl; else std::cout << "Index of "<< c << " not found" << std::endl; } std::optional<unsigned> find_index(const std::string& text, char c) { if (text.empty()) return std::nullopt; for(unsigned i{}; i < text.size();i++) { if(text[i]==c) { return i; } } return std::nullopt; }
Здесь добавлена функция print_index()
, которая выводит индекс найденного символа. При этом мы можем проверить значение optional
:
if(index)
Если optional равен std::nullopt
, то это условие возвратит false
. Таким образом, мы можем проверить на наличие значения. Консольный вывод программы:
Index of p: 4 Index of b not found
Тип optional
также предоставляет ряд функций. Некоторые из них:
has_value(): возвращает true
, если optional содержит значение.
Так, в примере выше мы могли бы заменить проверку
if(index)
на следующую строку
index.has_value()
value(): возвращает значение из optional. Так, в примере выше мы могли бы значение следующим образом
if(index.has_value()) std::cout << "Index of "<< c << ": " << index.value() << std::endl; else std::cout << "Index of "<< c << " not found" << std::endl;
Единственное, что надо помнить, что перед вызовом этого метода следует проверять на наличие значения.
value_or(default): если optional содержит значение, то возвращает это значение. Если значение в optional отсутствует, то возвращает аргумент default, который передается в функцию
Рассмотрим применение последней функции, которая может использоваться в тех ситуациях, когда необходимо определить для параметров значение по умолчанию:
#include <iostream> #include <optional> double pow(double, std::optional<unsigned> = std::nullopt); int main() { double n1 = pow(4, 3); std::cout << n1 << std::endl; // 64 double n2 = pow(4); // используем значение по умолчанию std::cout << n2 << std::endl; // 16 } double pow(double number, std::optional<unsigned> exp) { unsigned a = exp.value_or(2); double result{1.0}; for(unsigned i{}; i < a;i++) { result *= number; } return result; }
Здесь функция pow
принимает число, которое надо возвести в степень, и значение степени в виде std::optional<unsigned>
.
По умолчанию, если этому параметру не передано значение, то оно равно std::nullopt
.
В самой функции проверяем это значение, и если оно НЕ установлено, то возвращаем число 2 (то есть число будет возводиться в квадрат):
unsigned a = exp.value_or(2);