Я пишу код constexpr и хотел бы проверить вычисленные значения во время компиляции.
Обычный трюк состоит в том, чтобы сделать что-то вроде этого:
struct ReturnValue
{
int value1;
int value2;
};
constexpr ReturnValue doTheComputation()
{
return { .value1 = 10, .value2 = 20 };
}
template< auto t > struct p;
p< doTheComputation() > foo;
Это дает приятную ошибку компиляции:
implicit instantiation of undefined template 'p<ReturnValue{10, 20}>'
и я вижу ценность результата.
Однако это не работает для неструктурных типов, таких как std::string_view, std::string или чего-либо, напоминающего строку, поскольку такие типы не могут быть NTTP.
Нерабочий пример с std::string_view: https://godbolt.org/z/da8W557nK
Нерабочий пример с char const *: https://godbolt.org/z/5Mvqfx95q
Даже если я использую char const[ some_large_numer ], чтобы убедиться, что сообщение подходит, например это, вместо строки я получаю значения ASCII, перечисленные в качестве параметров шаблона:
implicit instantiation of undefined template 'p<ReturnValue{{115, 111, 109, 101, 32, 115, 101, 99, 114, 101, ...}}>
Существуют ли какие-либо советы или рекомендации по печати значений неструктурных типов во время компиляции (как результаты функций constexpr)?
Я видел неофициальный патч для GCC, который, видимо, решает эту проблему (я его не проверял), но я ищу решение для clang (Apple-clang из Xcode 15 или новее и LLVM 18 или новее) .
Просто добавьте подходящий форматтер и используйте p<std::format("{}", doTheComputation())>.
Спасибо за хитрость, иногда мне нужен быстрый способ вычислить sizeof() или offsetof.
@WeijunZhou, трюк с std::formatне работает по той же причине, которую я упомянул в исходном вопросе - std::format возвращает std::string, который не является структурным типом и, следовательно, не может использоваться в качестве NTTP.
@user12002570 user12002570 - Я это знаю. В целом использование {10, 20} считается плохой практикой, поскольку оно неоднозначно.
@DoDo В этом нет ничего двусмысленного. Тот, кто сказал вам, что это может вызвать двусмысленность, солгал, поскольку порядок уже зафиксирован при реализации класса. В вашем примере value1 объявляется перед value2, и это единственный способ их инициализации в {10, 20}. В этом нет никакой двусмысленности. Это может быть полезно/полезно, но не потому, что устраняет любую двусмысленность.
@user12002570 user12002570 Читателю все равно будет полезно, если определение ReturnValue находится далеко (например, в другом файле). Я предпочитаю быть откровенным.
@WeijunZhou Вам все равно нужно знать, какие имена членов нужно записать в списке обозначений, и для этого все равно нужно увидеть/поискать реализацию класса. Читателю это будет полезно, да.
Я имею в виду читателя, а не писателя. return Rectangle{.width=3, .height=4}; понятнее, чем return Rectangle{3,4} для тех, кто читает код. Я не говорю о том, чтобы написать это.
@WeijunZhou Да, для читателя, я согласен. Я вижу, вы обновили свой комментарий. Я также обновил свой комментарий и согласен, что он будет полезен читателю.





Вдохновленный идеей @WeijunZhou из комментариев к вопросу, я стал искать дальше. Я обнаружил, что в C++26 есть разновидность static_assert, которая позволяет второму параметру быть строковым результатом вычисления constexpr ( Ссылка на C++).
К сожалению, std::format не поддерживает constexpr даже в C++26 (cpp-ссылка подтверждает это), но я всегда могу написать свою функцию форматирования, которая выводит строкоподобный литеральный объект (согласно спецификации, ей нужно только предоставить функции .size() и .data()), поэтому я придумал следующее решение:
#include <string_view>
#include <string>
struct ReturnValue
{
std::string_view data;
std::string data2;
};
constexpr ReturnValue doTheComputation()
{
return { .data = "some secret message", .data2 = "owned data" };
}
constexpr std::string format( ReturnValue const & val )
{
std::string result{ val.data };
result += ", additional data: ";
result += val.data2;
return result;
}
static_assert( false, format( doTheComputation() ) );
Похоже, что это работает на LLVM 18, но пока не работает на Apple-Clang, поставляемом с Xcode 15.4 (мне все еще нужно протестировать бета-версию Xcode 16).
В целом это работоспособное решение, но я все равно был бы счастлив, если бы кто-нибудь придумал решение, которое я мог бы использовать в Xcode 15.4.
Судя по всему, эта функция не будет реализована в Xcode 16, согласно этому документу Apple.
Несвязано: вы можете уменьшить
return {.value1 = 10, .value2 = 20}доreturn {10,20}.