Всегда ли операции со строковым литералом должны быть constexpr?
Посмотрите этот код на Godbolt. Он содержит простую функцию, которая преобразует строку в целое число, игнорируя все нецифровые символы. Он также поддерживает анализ отрицательных целых чисел, если целочисленный тип подписан.
Поскольку функция constexpr
и вызывается со строковым литералом, GCC всегда оценивает функцию во время компиляции и выдает только одну инструкцию сборки. MSVC никогда этого не делает и всегда генерирует множество инструкций для обработки строкового литерала.
Однако clang выглядит странно - для целых чисел со знаком, если строковый литерал содержит 5 символов или меньше, функция вычисляется во время компиляции, в противном случае создается много ассемблерного кода. Для целых чисел без знака порог составляет 10 символов. Но если я заверну буквально вstd::string_view
, при clang он всегда вычисляется во время компиляции, независимо от длины строкового литерала.
Итак, какой компилятор "правильный"? Требует ли стандарт C++ обработки строковых литералов во время компиляции? Или это зависит от конкретного компилятора?
И почему clang генерирует код времени выполнения для определенного более длинного строкового литерала, в то время как он оценивает функцию во время компиляции для того же литерала, если он заключен в std::string_view
? я знаю этоstd::string_view
имеет constexpr
конструктор, что делает его еще более странным (прямая обработка во время компиляции невозможна, но вложенное построение временного объекта и его обработка во время компиляции в порядке - это похоже на большую работу для компилятора, чем в первом случае).
Для удобства вот функция по ссылке выше:
template< typename T, typename StringView >
constexpr T stringToInt( StringView && str ) noexcept requires std::is_integral_v< T >
{
T ret{ 0 };
auto isNegative = [ & ]() noexcept
{
if constexpr( std::is_signed_v< T > )
{
for ( auto && c : str )
{
if ( c == '-' ) return true;
if ( ( c >= '0' ) && ( c <= '9' ) ) return false;
}
return false;
}
else
return false;
}();
for ( auto && c : str )
{
if ( ( c >= '0' ) && ( c <= '9' ) )
{
ret = ret * 10 + c - '0';
}
}
return isNegative ? -ret : ret;
}
А некоторые вызывают примеры с комментариями, которые создает каждый компилятор:
return stringToInt< int >( "-5123" ); // single instruction on both GCC and Clang
return stringToInt< int >( "-51234" ); // single instruction on GCC, lots of instructions on clang
return stringToInt< int >( std::string_view{ "-51234" } ); // single instruction on both GCC and Clang
// MSVC in all those cases generates a lot of instructions