static_cast



В предыдущих статьях мы несколько раз упоминали оператор static_cast, поэтому мы решили затронуть еще и тему приведения типов. По мере развития серии, рассмотрим каждый из них, а завершим разбором C-style cast.



Исходя из своих наблюдений, наиболее востребованным оператором приведения является static_cast, т.к. в основном большинство приходится на преобразование между совместимыми друг с другом типами:

int32_t value_i32 = 42;

int64_t value_i64 = static_cast<int64_t>(value_i32);



float value_f32 = 42.314;

int16_t value_i16 = static_cast<int16_t>(value_f32);



Оператор static_cast так же проверяет корректность выполняемого приведения. Например, запрещает приведение указателя к значению:

// error: invalid 'static_cast' from type 'int*' to type 'int'

static_cast<int>(&value);



Конечно, некоторые смысловые ошибки нельзя поймать, ведь с точки зрения типа, все хорошо. Например, приведение значения к enum class может привести к непредвиденным сценариям 🤭:

enum class action_e : int { RUN = 0, FIGHT = 1 };



// Should I run or fight?

action_e action = static_cast<action_e>(2);



Лучше бы их все таки дополнять еще debug-only assert или вообще условным ветвлением.



Правила приведения для фундаментальных (встроенных) типов в C++ определены заранее, а вот для пользовательских классов можно определить свои собственные преобразования с помощью оператора приведения к типу: operator Type():

class specific_error_t

{

...

// Оператор приведение к типу `bool`

operator bool() const

{

return m_code < 0;

}

...

};



Эта ручка будет дергаться при явном и неявном приведении типов в живом примере 1:

specific_error_t internal_code = -1;



// Приведение `internal_code` к типу `bool`

bool has_internal_code = static_cast<bool>(internal_code);



Один из неочевидных способов применения этого оператора является приведение к типу void. Казалось бы, зачем? Но это помогает подавить предупреждение компилятора о неиспользуемой переменной / не присвоенном значении:

void foo()

{

    int result = read_and_do_something();



#ifdef DEBUG

    // Debug build check only

    assert(result == 0);

#endif



    static_cast<void>(result);

}





Если такое предупреждение появляется, то вероятно, что что-то вы все таки упускаете в своем коде. Но иногда такие ситуации встречаются, когда полезная нагрузка от вашего действия есть, а предупреждение не к месту. Например, в следствие какой-нибудь препроцессорной директивы. Напоминаем, что в C++17 так же есть атрибут [[maybe_unused]], который решает эту проблему.



Так же static_cast позволяет выполнить приведение к типу родительского класса (upcasting) и к типу наследников (downcasting) в рамках одной иерархии классов:

Child *pointer   = new Child();



// Upcasting

Base *base_ptr = static_cast<Base*>(pointer);



// Downcasting

Child *child_ptr = static_cast<Child*>(base_ptr);



Важным моментом является тот факт, что static_cast не может обеспечить проверку корректности совершенного преобразования к наследнику (downcasting)! Если наследник выбран неправильно и вы допустили ошибку преобразования к другому типу, то вам все равно дадут скомпилироваться: живой пример 2. У компилятора действительно не хватает информации, чтобы это проверить на этапе компиляции.



Разберем эту тему подробнее, когда дойдем до динамического полиморфизма