Pull to refresh

Comments 29

Ну если вы уже в макросы (то есть в глобальную область видеомости) полезли, то что мешало вам определить какой-нибудь
template <char... Chars>
String<Chars...> operator""_conststr() {
  return String<Chars...>();
}
Проще и не менее удобно IMO…
К сожалению, я подумал о возможности сделать вывод типа через кастомный литерал только когда заканчивал готовить этот текст и не успел проверить возможность реализации этого без макросов. При первом приближении подумалось, что:
1. Понадобится комбинировать такой литерал и decltype(), чтобы добиться того же эффекта.
2. Не очень тривиально вывести возвращаемый тип из строки.

Ну то есть будет выглядеть как-то вроде вот этого:
constexpr auto operator""_conststr(const char* data, const size_t size) {
  return ...;
}

using FooString = decltype("Foo"_conststr);


Я еще надеюсь проверить эту идею и возможно написать отдельное короткое дополнение к этому посту.
Извиняюсь: не заметил, что это вам не для решения задачи, а для какого-то фреймфорка абстрактного.

Может кто-нибудь попробует внести в C++20 это расширение, что ли?

Потому что то, что есть в C++17 — это издевательство: вы можете получить строку в виде последовательности charов, если… эта строка — это число! Великолепная идея, блин: сделать фичу — но так, чтобы использовать её можно было ровно тогда, когда она никому нафиг не нужна… а то, что нужно — это, оказывается… GNU'сное расширение…
Весьма не абстрактного, а очень даже популярного и широкоиспользуемого. И этот фреймворк только-только хочет перестать поддерживать компиляцию без C++11. Так что вкоммитить такое в него будет сложно. Разве что под каким-нибудь фиче-флагом. Ну или будет жить в форке.
Прошу прощения, был не знаком с N3599. Если его использовать, то все действительно проще пареной репы.

К сожалению, несмотря на то, что это поддерживается в gcc и clang, насколько я понял, это предложение до сих пор не вошло в стандарт (все, что я смог найти, говорит о том, что это предложение сейчас в статусе Open и вообще не обсуждалось комитетом с 2015 года). Если переключить в вашем примере компилятор на последний доступный msvc, то он не скомпилирует этот код, причем с довольно забавным набором ошибок.

Мне очень нравится clang и я предпочитаю везде использовать именно его, но я не люблю писать compiler-specific код, не предусмотренный стандартом. По моему опыту это имеет свойство через некоторое время превращаться в очередные подводные грабли.

Если же использовать тот literal operator, который есть в стандарте, то нужно действительно повозиться с тем, чтобы вывести содержимое этого литерала в аргументы шаблона. Мне кажется, что это все-таки реально сделать в constexpr, но я в этом не уверен.
P0424R2 — это 2017й. Но у нас clang основной компилятор, так что особо не парило — работает и работает.

А позиция разработчиков MSVC простая — добавят а стандарт, тогда и реализуем… Как раз попросить ребят из Яндекса «замолвить словечко»…
Мне очень нравится clang и я предпочитаю везде использовать именно его, но я не люблю писать compiler-specific код, не предусмотренный стандартом. По моему опыту это имеет свойство через некоторое время превращаться в очередные подводные грабли.
Не в тех случаях, когда это включено в следующую версию стандарта, как в данном случае. Спасибо eao197!
Есть вариант без макросов и «compiler-specific» кода — лямбды. Да, «строгость типизации» теряется, но зато всё довольно красиво. Достаточно передать лямбду в constexpr функцию, которая вызовет эту лямбду и передаст возвращаемое значение в шаблон. Как-то так:
Код
#include <iostream>
#include <utility>

template <char... symbols>
struct Constexpr_string
{
  static constexpr char value[] = { symbols... };
};

template <typename Lambda_type, std::size_t... indexes>
constexpr auto get_string(Lambda_type function, std::index_sequence<indexes...>)
{
  return Constexpr_string<function()[indexes]...>();
}

template <std::size_t N, typename Lambda_type, typename Indices = std::make_index_sequence<N>>
constexpr auto get_string(Lambda_type function)
{
  return get_string(function, Indices{});
}

constexpr size_t length(const char* str)
{
    return (*str == 0) ? 0 : length(str + 1) + 1;
}

template <typename Lambda_type>
constexpr auto get_string(Lambda_type function)
{
  return get_string<length(function())>(function);
}

int main()
{
    auto s = get_string([](){ return "lambda"; });
    std::cout << decltype(s)::value << std::endl;
}

Мне было бы очень интересно услышать о возможных применениях этого механизма, отличных от тех, что придумал я.

Но вы же тоже не привели примеры того, где это может пригодиться :)


Конкретные задачи, которые я решал — отдельный вопрос.

Наверное стоило всё-таки дописать эту часть.

Я не стал писать эту часть, потому что сами по себе применения получаются очень интересными и стоят отдельного поста :). Ну и прежде чем о них рассказывать хочется сделать так, чтобы было что показывать.

Один пример подразумевает очень вкусное расширение для одного очень популярного open-source фреймворка. Я имплементирую это дело до конца, попробую вкоммитить в фреймворк и если не получится завести форк. И тогда уже расскажу со всеми подробностями.

Другой пример возник в ходе наколеночного проекта, который я пытаюсь делать для самообразования и просто в качестве развлечения. И я хочу сначала сделать из этого что-то относительно законченное, что продемонстрирует то, что приносит такой подход решения проблемы. Будет что-то готовое к показу — расскажу и покажу.
И это все потому, что разработчики языка не разрешили использовать обычные человеческие константные строки в качестве параметров шаблонов…
Да, не хватает сахара в языке для строк в шаблоне:
template<char ... c>
class String { ... };
String<"test">{};

А использовать String<'t', 'e', 's', 't'>{}; — убожество.
И СИ макросы особо не решают эту проблему.
Они убоялись, что туда будут пихать строки на килобайт и компилятор будет торомозить… как будто сейчас мало способов заставить его это делать…
constexpr const char _test[] = "12345";
template <const char String[]>
struct CString
{
	static inline constexpr const std::string_view str{String};
	static inline constexpr const char *psz = String;

	static constexpr bool verify() noexcept
	{
		for (size_t i = 1, n = str.size(); i < n; ++i)
			if (psz[i - 1] > psz[i])
				return false;

		return true;
	}

	static_assert(verify());
};

 std::cout << CString<_test>::str << CString<_test>::psz;
тут проблема в том, что _test должна быть глобальной и в отдельной переменной, и код типа:
std::cout << CString<"123456">::str << CString<_test>::psz;

просто не скомпилится.
Что значит «глобальной»?
int main()
{
    static constexpr const char _test[] = "12345";
    std::cout << CString<_test>::str << CString<_test>::psz;
	return 0;
}
Это значит, что у переменной должно быть имя.

«Внтури компилятора» имя переменной (в данном случае _test) и всех охватывающих её объектов становится частью имени создаваемого класса. Нет глобально-уникалтного имени переменной — нет класса (здесь глобальность понимается именно в смысле «глобально-уникалтный для всей программы», а не в смысле C++).

Как цирковой трюк (ну или для собеседования) — годится, как что-то для практического использования — нет.

Если вы уже создали строку C-style, то её можно уже и использовать везде, где нужно, и не морочить людям голову.
Мне было бы очень интересно услышать о возможных применениях этого механизма, отличных от тех, что придумал я.

Type-safe printf. Например у выражения:
myprintf("Hello % world %!\n", 1, "123") 

На выходе получится вызов:
printf("Hello %d world %s\n", 1, "123");


Compile-time строки я использую в своем логгере (предподготовка и валидация форматной строки). Правда в новой версии я заюзал boost hana вместо велосипеда.

Самое крутое, что я видел с compile-time строками — это CTRE
И string_view тоже ведь читается compile-time.

P..S. Возьмите fmtlib. Поддерживает printf-style и python-style.
Есть spdlog на его основе, но я бы не рекомендовал его брать, ибо он header-only и ради какой-то ерунды инклюдит вам windows.h
fmtlib только валидирует формат строку, без какой либо compile-time магии со строками.
Ну и spdlog достаточно медленный для моих задач. Форматирование inplace — это дорого.
И оно там весьма скудное)
Я, когда делал давно свой логгер, формат задавал единожды и единожды его преобразовывал в последовательность команд.
Вы реализовали форматирование в своем логгере?

P.S. я чет первым делом подумал про формат сообщений, а не разбор параметров.
Вы реализовали форматирование в своем логгере?

Да, с минимальной поддержкой типов и флагов форматирования. Тут фишка в том, что форматирование (да и запись в файл) происходит в отдельном процессе.
А по производительности есть сравнение в цифрах?
> ./logging-bench
2019-01-31 18:37:47
Running ./logging-bench
Run on (8 X 4200 MHz CPU s)
CPU Caches:
L1 Data 32K (x4)
L1 Instruction 32K (x4)
L2 Unified 256K (x4)
L3 Unified 8192K (x1)
***WARNING*** Library was built as DEBUG. Timings may be affected.
--------------------------------------------------------------
Benchmark Time CPU Iterations
--------------------------------------------------------------
bmLoggingSingleString 121 ns 120 ns 5835101
bmLogging1Arg 134 ns 134 ns 5213675
bmLogging2Arg 156 ns 155 ns 4504116
bmLogging3Arg 218 ns 215 ns 3255618
bmLogging5StringArgs 538 ns 526 ns 1346569
bmLogging2ArgWithSpecs 168 ns 164 ns 4299307

Это в дебаге, в релизе на порядок (или 2) меньше
Вот мой вариант без портянки вложенных шаблонов, но при этом с портянкой извлечения отдельных char'ов по индексу (до 128 символов):

#include <cstdlib>
#include <array>

template<char ... chars>
struct ct_string
{
    constexpr static size_t size()
    {
        return sizeof...(chars);
    }
    
    constexpr static auto get()
    {
        return std::array<char, size()>{ {chars ...} };
    }
    
    constexpr static auto to_string()
    {
        return std::string{chars ...};
    }
};


template<size_t N, typename T, char ...>
struct ct_maker_details;

template<size_t N, char ... chars, char first, char ... tail>
struct ct_maker_details<N, ct_string<chars...>, first, tail...>
{
    static constexpr auto make()
    {
        if constexpr(sizeof...(chars) == N)
            return ct_string<chars ...>{};
        else
            return ct_maker_details<N, ct_string<chars ..., first>, tail ...>::make();
    };
};

template<size_t N, char ... chars>
constexpr auto make_ct_string()
{
    return ct_maker_details<N, ct_string<>, chars...>::make();
};

template<size_t size>
constexpr size_t str_size(const char (&)[size])
{
    return size;
}

#define STR_TO_CHARS(str)  STR_TO_CHARS_(str, str_size(str))
#define STR_TO_CHARS_(str, N)  \
    str[IC(0, N)], str[IC(1, N)], str[IC(2, N)], str[IC(3, N)], str[IC(4, N)], str[IC(5, N)], str[IC(6, N)], str[IC(7, N)], str[IC(8, N)], str[IC(9, N)],  \
    str[IC(10, N)], str[IC(11, N)], str[IC(12, N)], str[IC(13, N)], str[IC(14, N)], str[IC(15, N)], str[IC(16, N)], str[IC(17, N)], str[IC(18, N)], str[IC(19, N)],  \
    str[IC(20, N)], str[IC(21, N)], str[IC(22, N)], str[IC(23, N)], str[IC(24, N)], str[IC(25, N)], str[IC(26, N)], str[IC(27, N)], str[IC(28, N)], str[IC(29, N)],  \
    str[IC(30, N)], str[IC(31, N)], str[IC(32, N)], str[IC(33, N)], str[IC(34, N)], str[IC(35, N)], str[IC(36, N)], str[IC(37, N)], str[IC(38, N)], str[IC(39, N)],  \
    str[IC(40, N)], str[IC(41, N)], str[IC(42, N)], str[IC(43, N)], str[IC(44, N)], str[IC(45, N)], str[IC(46, N)], str[IC(47, N)], str[IC(48, N)], str[IC(49, N)],  \
    str[IC(50, N)], str[IC(51, N)], str[IC(52, N)], str[IC(53, N)], str[IC(54, N)], str[IC(55, N)], str[IC(56, N)], str[IC(57, N)], str[IC(58, N)], str[IC(59, N)],  \
    str[IC(60, N)], str[IC(61, N)], str[IC(62, N)], str[IC(63, N)], str[IC(64, N)], str[IC(65, N)], str[IC(66, N)], str[IC(67, N)], str[IC(68, N)], str[IC(69, N)],  \
    str[IC(70, N)], str[IC(71, N)], str[IC(72, N)], str[IC(73, N)], str[IC(74, N)], str[IC(75, N)], str[IC(76, N)], str[IC(77, N)], str[IC(78, N)], str[IC(79, N)],  \
    str[IC(80, N)], str[IC(81, N)], str[IC(82, N)], str[IC(83, N)], str[IC(84, N)], str[IC(85, N)], str[IC(86, N)], str[IC(87, N)], str[IC(88, N)], str[IC(89, N)],  \
    str[IC(90, N)], str[IC(91, N)], str[IC(92, N)], str[IC(93, N)], str[IC(94, N)], str[IC(95, N)], str[IC(96, N)], str[IC(97, N)], str[IC(98, N)], str[IC(99, N)],  \
    str[IC(100, N)], str[IC(101, N)], str[IC(102, N)], str[IC(103, N)], str[IC(104, N)], str[IC(105, N)], str[IC(106, N)], str[IC(107, N)], str[IC(108, N)], str[IC(109, N)],  \
    str[IC(110, N)], str[IC(111, N)], str[IC(112, N)], str[IC(113, N)], str[IC(114, N)], str[IC(115, N)], str[IC(116, N)], str[IC(117, N)], str[IC(118, N)], str[IC(119, N)],  \
    str[IC(120, N)], str[IC(121, N)], str[IC(122, N)], str[IC(123, N)], str[IC(124, N)], str[IC(125, N)], str[IC(126, N)], str[IC(127, N)]

#define IC(index, count)  (index) >= (count) ? ((count) - 1) : (index)

#define CT_STR(str) decltype(make_ct_string<str_size(str), STR_TO_CHARS(str)>())


#include <iostream>

template<typename T>
void foo()
{
    std::cout << "size=" << T::size() << " string: " << T::to_string() << std::endl;
};


int main()
{   
    foo<CT_STR("Hello world!!!!")>();    
    return 0;
}


Тут ещё конечно можно более строго подойти: поскольку используется decltype наверняка можно было бы избавиться от имплементации функции вычислив возвращаемый тип в каком-нибудь std::conditional, да и уменьшить количество шалонов использовав например CharAt из статьи, но в качестве отправной точки думаю сгодиться :)

ссылка coliru
Sign up to leave a comment.

Articles