Какова продолжительность хранения и время существования параметра шаблона, не являющегося типом, и как его можно использовать для вычислений во время компиляции?

В 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 29.02.2024 15:31

Аргументы шаблона, типа или не типа, используются только во время компиляции. Они не существуют после завершения работы компилятора. Поэтому вопрос «Какова продолжительность статического хранения и время жизни параметра шаблона, не являющегося типом», является спорным. У них нет класса хранения, продолжительности, связи или срока службы.

Some programmer dude 29.02.2024 15:35

Вы не дали ссылку на свой пример, поэтому мы понятия не имеем, в чем ваша проблема. Опубликуйте полностью автономный минимально воспроизводимый пример и мы сможем помочь.

NathanOliver 29.02.2024 15:39

@user12002570 user12002570 код довольно длинный по отношению к основной проблеме использования NTTP. Я боялся, что это больше запутает читателя, чем поможет. Можете ли вы посмотреть видео по ссылке примерно через 20 минут? Если вы все еще считаете, что это полезно, я потрачу время, чтобы воспроизвести этот код.

Oersted 29.02.2024 15:41

@Someprogrammerdude Кажется, ваш комментарий указывает на то, что я неправильно понимаю стандартную цитату, и цель моего вопроса - прояснить ее ...

Oersted 29.02.2024 15:43

«Может ли кто-нибудь [...] объяснить, как работает вариант использования Джейсона Тернера?» vs «Я боялся, что это больше запутает читателя, чем поможет». вам нужно решить. Опустите это, потому что это сбивает с толку, или ожидайте, что читатель объяснит это;)

463035818_is_not_an_ai 29.02.2024 15:44

@463035818_is_not_an_ai ОК, я в деле...

Oersted 29.02.2024 15:58

@Oersted clang багажник, однако, доволен этим

Ted Lyngmo 29.02.2024 17:02
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
9
8
228
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Проблема в том, что правила различаются в зависимости от типа параметра шаблона, не являющегося типом.

  • если это тип класса, то идентификаторное выражение, именующее его, будет lvalue типа const T.
  • если это тип, не относящийся к классу, то выражение идентификатора, именующее его, будет значением prvalue типа 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

Другие вопросы по теме