Недавно у себя в G+ ленте я дал ссылку на свою первую попытку реализации конкатенации строк в compile-time: https://godbolt.org/g/GWLTN8. Эта версия работала под GCC, но не работала под clang. Причем, как мне представляется, прав в этой ситуации именно clang: ведь constexpr-функция может быть запущенна не только в compile-time, но и в run-time. И clang мог посчитать, что в каких-то контекстах операции, выполняющие обработку аргументов функции, не могут быть использованы.
Дабы избавиться от этой проблемы я нашел другой способ подсчета размерности результирующией строки. В результате код собирается компиляторами clang 3.5-3.8 и gcc 5.1-6.1 с ключиком -std=c++14. Поиграться с кодом можно здесь: https://godbolt.org/g/G5wdyD (так же полный код примера под катом).
Отдельно стоит сказать про выхлоп компилятора. Очевидно, что содержимое результирующей строки формируется в compile-time, иначе бы не работали проверки в static_assert. Но вот как это содержимое добавляется в код зависит от компилятора: clang явно размещает результирующую строку как строковый литерал и затем использует адрес этого литерала. А вот gcc использует серию команд movb для инициализации значения в run-time.
Задачка эта всплыла в большущем LOR-овском флейме. Еще один товарищ из этого обсуждения предложил несколько своих вариантов. Перечислены они здесь: https://www.linux.org.ru/forum/development/12649936?cid=12674932 (пояснительный текст с ссылками на godbolt) и здесь: https://www.linux.org.ru/forum/development/12649936?cid=12675192 (полные тексты). Данные решения используют либо ключи -std=c++1z (т.е. требуют фич из не утвержденного еще стандарта C++17), либо используют GNU-расширения языка. Последнее из решений, в котором используется GNU-тое расширение для формирование compile-time строковых литералов посредством суффикса _ct, дает для clang и GCC одинаковый выхлоп: инициализация строки в run-time через movl.
Какой-то практической значимости у этой форумной задачки не видно. Может быть, подобные фокусы смогут оказаться полезными, когда в программу нужно вшивать base64 представления каких-то строк. Или если в программе нужны строки для представления URL или имен MQ-шных топиков, склеенные из частей, которые известны на этапе компиляции. Но лично я бы в таких случаях предпочел бы pre-compile-time генерацию строковых литералов.
Однако, в плане того, чтобы поразбираться с фичами современного C++ эта задачка очень полезная. Т.к. в интернете можно нагуглить несколько вариантов решения подобной задачи. И далеко не всегда понятно, как и почему это все работает. Ну и самым полезным результатом лично для меня стало открытие вот такой конструкции:
template< std::size_t ...L > constexpr auto ct_concat( const char (&...args)[L] ) |
Эта конструкция позволяет записать шаблон функции с переменным количеством аргументов, аргументами которой должны быть строковые литералы.
Ну а теперь, собственно, то, что у меня получилось.
Код, наверное, довольно многословный. Но мне так проще писать код и затем проще в нем разбираться.
#include <utility> #include <iostream> template< std::size_t N > struct ct_cstringz { char data_[ N ]; constexpr char * data() { return data_; } constexpr const char * data() const { return data_; } constexpr char & operator[]( std::size_t i ) { return data_[i]; } constexpr const char & operator[]( std::size_t i ) const { return data_[i]; } }; namespace ct_details { template< std::size_t N > constexpr char * copy( const char (&s)[N], char * to ) { const char * b = s, * e = s + N - 1; while( b != e ) *(to++) = *(b++); return to; } template< std::size_t C, std::size_t L, std::size_t... T > struct size_detector_impl { static constexpr const std::size_t value = L - 1 + size_detector_impl< sizeof...(T), T... >::value; }; template<std::size_t L> struct size_detector_impl<1, L> { static constexpr const std::size_t value = L; }; template< std::size_t... T > struct size_detector { static constexpr const std::size_t value = size_detector_impl< sizeof...(T), T... >::value; }; constexpr char * copy_to( char * to ) { return to; } template< std::size_t N, typename... ARGS > constexpr char * copy_to( char * to, const char (&s)[N], ARGS &&... args ) { char * p = copy( s, to ); return copy_to( p, std::forward<ARGS>(args)... ); } } /* namespace ct_details */ template< std::size_t ...L > constexpr auto ct_concat( const char (&...args)[L] ) { using namespace ct_details; ct_cstringz< size_detector< L... >::value > result{}; char * p = copy_to( result.data(), args... ); *p = 0; return result; } template< std::size_t N0, std::size_t N1, std::size_t N2 > constexpr auto dynamic_domain_name_at_compile_time( const char (&s0)[N0], const char (&s1)[N1], const char (&s2)[N2] ) { return ct_concat( s0, ".", s1, ".", s2 ); } int main() { constexpr auto r = ct_concat( "linux", "org", "ru" ); static_assert( 11 == sizeof(r), "Unexpected size!" ); static_assert( 'l' == r[0], "Expected 'l'" ); static_assert( 'i' == r[1], "Expected 'i'" ); static_assert( 'x' == r[4], "Expected 'x'" ); static_assert( 'o' == r[5], "Expected 'o'" ); puts( r.data() ); const char domain[] = "linux.org.ru"; constexpr auto dynamic = dynamic_domain_name_at_compile_time( "linux", "org", "ru" ); static_assert( sizeof(domain) == sizeof(dynamic), "Oops!" ); puts( dynamic.data() ); } |
Комментариев нет:
Отправить комментарий