Начиная с версии стандарта 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);