Как проверить возможность преобразования типов? C++

Допустим есть функция, которая в зависимости от возможности преобразовать аргумент к тому или иному типу возвращает разное значение. Как проверить возможность преобразовать тип? Если возможно, то вернуть значение 1, если нет, то значение 2? Абстрактный код:

template<typename T>
Base my_cast(T mclass) {

    if (convertable_to_Base)
        return { mclass };
    else
        return Base::NULL;

}

Ответы (1 шт):

Автор решения: HolyBlackCat

Способов преобразования много разных. Почти столько же, сколько видов инициализации...

Для самых распространенных из них в стандартной библиотеке есть trait-ы, например:

  • std::is_convertible_v - преобразование возможно без каста,
  • std::is_constructible_v - преобразование возможно при создании переменной: тип переменная(выражение);.

Примеры использования см. в конце ответа.

Первое гарантирует (обычно?), что вообще любое преобразование сработает, в т. ч. неявное. По идее, первое гарантирует второе.

Второе гарантирует, например, что static_cast сработает, но не гарантирует что сработает неявное преобразование.

Даже если они оба вернули false, какой-нибудь static_cast все равно может сработать.

Если вам не подходят стандартные trait-ы (потому что вы проверяете более редкий способ преобразования), то вы можете написать свой подобный trait с помощью SFINAE.

В C++20 вы также можете использовать requires. Он удобен тем, что не нужно помнить, какой trait проверяет какой синтаксис. Вы просто втыкаете интересующий вас синтаксис в requires, см. пример в конце.

Независимо от выбранного способа проверки, нужно использовать if constexpr вместо if. Обычный if проверяет обе ветки на валидность, даже если одна из них никогда не будет выполнена, поэтому если преобразование для какого-то типа невозможно, вы получили бы ошибку компиляции независимо от того, что вы поставили в условие.

Пример раз:

template <typename To, typename From>
To my_cast(From value)
{
    if constexpr (std::is_constructible_v<To, From &>)
        return To(value); 
    else
        return {};
}

Или, что более правильно:

template <typename To, typename From>
To my_cast(From &&value)
{
    if constexpr (std::is_constructible_v<To, From>)
        return To(std::forward<From>(value)); 
    else
        return {};
}

Обычно этого достаточно.

Однако is_constructible - это более жесткое требование, чем возможность сделать сишный каст To(value). Например,

enum class E {e = 42};
std::cout << my_cast<int>(E::e) << '\n';

выводит 0, хотя такой каст возможен.

Если вас это не устраивает, то тут очень удобен requires:

template <typename To, typename From>
To my_cast(From &&value)
{
    if constexpr (requires{To(std::forward<From>(value))})
        return To(std::forward<From>(value)); 
    else
        return {};
}

Он позволяет проверить на валидность именно тот преобразование, которое вы собираетесь сделать.

→ Ссылка