В C++ Weekly — Ep 313 — Проблема constexpr, на решение которой мне потребовалось 5 лет!, Джейсон Тернер демонстрирует несколько методов времени компиляции, позволяющих создать std::string
во время компиляции, а затем передать его std::string_view
для использования во время выполнения.
Основная проблема заключается в том, что std::string
использует new
, созданное во время компиляции std::string
должно использоваться в одном выражении, поскольку new
должно иметь соответствующее delete
внутри того же constant/compile-time context
(извините за приблизительную формулировку).
Поскольку код довольно длинный, я поставил его в конце своего вопроса.
Для этого он сначала показывает, как данные из std::string
можно скопировать в std::array
, сохраняя их внутри функций consteval
(на данный момент я не уверен, что этого нельзя достичь просто с помощью функции constexpr
).
Чего я вообще не понимаю, так это того, как он «продлевает» время жизни std::array
за пределы непосредственного вызова функций с использованием параметра шаблона , не являющегося типом (NTTP), примерно на 19 минут.
Мое (вероятно, неправильное) понимание из стандарта заключается в том, что NTTP связан с template parameter object
со статической длительностью хранения:
Выражение id, именующее нетиповой параметр шаблона типа класса T, обозначает статический объект продолжительности хранения типа const T, известный как объект параметра шаблона, который эквивалентен аргументу шаблона ([temp.type]) объекту соответствующий аргумент шаблона после его преобразования в тип параметра шаблона ([temp.arg.nontype]). Никакие два объекта параметров шаблона не являются эквивалентными аргументам шаблона.
Тем не менее, я попытался поиграться с гораздо более простым (и надуманным) примером:
template <auto Value>
consteval const auto& make_static() {
return Value;
}
int main() {
[[maybe_unused]] const auto& i = make_static<2>(); // (1)
[[maybe_unused]] static const auto& j = make_static<2>(); // (2)
[[maybe_unused]] static constexpr auto& k = make_static<2>(); // (3)
[[maybe_unused]] constinit static auto& l = make_static<2>(); // (4)
return i;
}
Ни одна из многих попыток не увенчалась успехом, как видно из предоставленной связанной демо-версии. Разные составители ставят мне разные диагнозы.
Обычно make_static<2>()
не считается постоянным выражением и возвращает ссылку на временный объект (что, насколько я понимаю, превосходит цель Джейсона Тернера).
Примечание: GCC 13.2 (27 июля 2023 г.) даже жалуется на разыменование nullptr
, но магистральная версия кажется более совместимой с MSVC и Clang.
Что не так с моим примером и как в этом свете работает вариант использования Джейсона Тернера?
#include <algorithm>
#include <array>
#include <cstddef>
#include <format>
#include <iostream>
#include <string>
// Some algorithm to generate a fancy std::string, possibly at compile-time
constexpr std::string make_string(std::string_view base,
const std::size_t repeat) {
std::string retval;
for (std::size_t count = 0; count < repeat; ++count) {
retval += base;
}
return retval;
}
// Use only at compile-time: it must be large enough to hold
// a copy of the string data in "constant evaluated context"
// NB: It is probably wrong wording above
struct oversized_array {
//static constexpr std::size_t VeryLargeSize = 10 * 1024 * 1024; // Jason
//turner example
static constexpr std::size_t VeryLargeSize =
1000; // Jason Turner value was too large for MSVC on Compiler Explorer
std::array<char, VeryLargeSize> data{};
std::size_t size; // Actually the used size, VeryLargeSize max.
};
// Copy a string containt into an array "large enough".
// It is a helper for to_right_sized_array below
constexpr auto to_oversized_array(const std::string &str) {
oversized_array result;
std::copy(str.begin(), str.end(), result.data.begin());
result.size = str.size();
return result;
}
// Copy a string containt into an array of the same size.
// The callable is a lambda that is wrapping make_string.
// KO FOR CLANG
template <typename Callable>
consteval auto to_right_sized_array(
Callable callable) { // Jason Turner version
// constexpr auto to_right_sized_array(Callable callable) { // It seems to be
// enough
constexpr auto oversized = to_oversized_array(callable());
std::array<char, oversized.size> result;
std::copy(oversized.data.begin(),
std::next(oversized.data.begin(), oversized.size),
result.begin());
return result;
}
// Merely returns a reference to the NTTP 'Data'.
// THE CORE OF MY QUESTION IS HERE
template <auto Data>
consteval const auto &make_static() {
return Data;
}
// Wrapping the array-converted string into a string_view
template <typename Callable>
consteval auto to_string_view(Callable callable) {
// std::array as NTTP is C++20?
constexpr auto &static_data = make_static<to_right_sized_array(callable)>();
return std::string_view{static_data.begin(), static_data.size()};
}
int main() {
constexpr auto make_data = []() {
return make_string("Hello compile-time world,", 3);
};
constexpr static auto view = to_string_view(make_data);
std::cout << std::format("{}: {}", view.size(), view);
}
Примечание: Эту версию принимает только GCC. Кажется, MSVC путают с std::string_view
API (или, может быть, я допустил опечатку при копировании кода Джейсона Тернера). Кланг не согласен со всеми этими постоянными оценками. Это может быть связано с одним из моих предыдущих постов: GCC и Clang ведут себя по-разному в отношении постоянной оценки
Аргументы шаблона, типа или не типа, используются только во время компиляции. Они не существуют после завершения работы компилятора. Поэтому вопрос «Какова продолжительность статического хранения и время жизни параметра шаблона, не являющегося типом», является спорным. У них нет класса хранения, продолжительности, связи или срока службы.
Вы не дали ссылку на свой пример, поэтому мы понятия не имеем, в чем ваша проблема. Опубликуйте полностью автономный минимально воспроизводимый пример и мы сможем помочь.
@user12002570 user12002570 код довольно длинный по отношению к основной проблеме использования NTTP. Я боялся, что это больше запутает читателя, чем поможет. Можете ли вы посмотреть видео по ссылке примерно через 20 минут? Если вы все еще считаете, что это полезно, я потрачу время, чтобы воспроизвести этот код.
@Someprogrammerdude Кажется, ваш комментарий указывает на то, что я неправильно понимаю стандартную цитату, и цель моего вопроса - прояснить ее ...
«Может ли кто-нибудь [...] объяснить, как работает вариант использования Джейсона Тернера?» vs «Я боялся, что это больше запутает читателя, чем поможет». вам нужно решить. Опустите это, потому что это сбивает с толку, или ожидайте, что читатель объяснит это;)
@463035818_is_not_an_ai ОК, я в деле...
@Oersted clang багажник, однако, доволен этим
Проблема в том, что правила различаются в зависимости от типа параметра шаблона, не являющегося типом.
const T
.T
.Обратите внимание, что цитируемый вами раздел применим только к параметрам шаблона типа класса, не являющегося типом.
Но вы передаете int (который не является типом класса), поэтому параграф (8) в этом случае не применяется.
13.2 Параметры шаблона [temp.param]
(8) Выражение id, именующее нетиповой параметр шаблона типа класса T, обозначает статический объект продолжительности хранения типа const T, известный как объект параметра шаблона, значение которого равно значению соответствующего аргумента шаблона после его преобразования в тип параметра-шаблона. Все подобные параметры шаблона в программе одного типа с одинаковым значением обозначают один и тот же объект параметра шаблона. Объект параметра шаблона должен иметь постоянное разрушение.[Примечание 3. Если в id-выражении указан параметр шаблона, не являющийся типом и не являющийся ссылкой, то это значение prvalue, если оно имеет тип, отличный от класса. В противном случае, если он принадлежит к типу класса T, это lvalue и имеет тип const T. — последнее примечание]
Таким образом, Value
будет значением prvalue в make_static
, и попытка привязать ссылку к нему материализует временное значение.
Таким образом, это эффективно пытается вернуть висячую ссылку.
Обходной путь для этого — обернуть параметры шаблона, не относящиеся к типу класса, в параметр шаблона, не относящийся к типу класса, и вернуть ссылку на него, например: godbolt
#include <type_traits>
template<class T>
struct value_holder { T value; };
template<auto V>
consteval auto const& make_static() {
if constexpr(std::is_class_v<decltype(V)>) {
return V;
} else {
// V is of non-class type, and therefore a prvalue
// => wrap it in a class type so that we can return a reference
return make_static<value_holder{V}>().value;
}
}
Джейсон Тернер повозился со струной, в принципе он все сделал правильно. Turtlefight объяснил причину проблемы. Вот так компактнее решается - но это не точно. :) https://godbolt.org/z/f83n473Pq
Как сейчас написано, ваш ответ неясен. Пожалуйста, отредактируйте , чтобы добавить дополнительную информацию, которая поможет другим понять, как это относится к заданному вопросу. Более подробную информацию о том, как писать хорошие ответы, вы можете найти в справочном центре.
«объясните, как работает вариант использования Джейсона Тернера?...» Вы должны опубликовать здесь фрагмент кода Джейсона, чтобы нам не приходилось просматривать видео и статью, чтобы увидеть его код. Сообщение должно быть полным, поскольку оно должно содержать всю необходимую информацию (в данном случае код, с которым вы сравниваете) внутри самого сообщения.