Почему std::array не является постоянным выражением, когда он является входом шаблонной функции/универсальной лямбды?

(Относится к этому другому моему вопросу; если вы тоже посмотрите на это, я был бы очень признателен.)

Если std::array<T,N>::size is constexpr, то почему следующий код даже не компилируется?

#include <array>
#include <iostream>

constexpr auto print_size = [](auto const& array){
    constexpr auto size = array.size();
    std::cout << size << '\n';
};

int main() {
    print_size(std::array<int,3>{{1,2,3}});
}

Ошибка следующая:

$ g++ -std=c++17 deleteme.cpp && ./a.out 
deleteme.cpp: In instantiation of ‘<lambda(const auto:1&)> [with auto:1 = std::array<int, 3>]’:
deleteme.cpp:10:42:   required from here
deleteme.cpp:5:20: error: ‘array’ is not a constant expression
    5 |     constexpr auto size = array.size();
      |                    ^~~~

Но мне интересно, почему.

На месте вызова лямбда аргумент известен во время компиляции, и лямбда должна быть создана с auto равным std::array<int,3>, где 3 — значение времени компиляции, поэтому на выходе должно быть array.size().

Что не так в моих рассуждениях?

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

вернуть array.size(); работает.
Ted Lyngmo 10.12.2020 15:40

@TedLyngmo, это делает ситуацию еще более странной. Возможно, RVO обходит какое-то ограничение языка?

Enlico 10.12.2020 15:46

Действительно. constexpr size = array.size(); внутри лямбды не будет работать, но возврат того же и присвоение переменной constexpr работает. Понятия не имею, почему существует разница :-)

Ted Lyngmo 10.12.2020 15:49

Параметры функции никогда не бывают constexpr. Помните, что даже функцию constexpr нужно вызывать во время выполнения.

super 10.12.2020 15:49

@super Верно, но в таком случае это выглядит странно.

Ted Lyngmo 10.12.2020 15:56

@TedLyngmo Действительно. Я помню, как где-то видел хорошую статью/видео об этом здесь, на SO. Это углубляется и объясняет, почему это (не уверен, что это точный сценарий, но, по крайней мере, похоже) даже невозможно с consteval. Я посмотрю, смогу ли я найти его.

super 10.12.2020 15:58

Вы можете использовать constexpr auto size = std::tuple_size<std::decay_t<decltype(array)>>::value.

Jarod42 10.12.2020 16:02

@super, как пример Теда Люнгмо избегает этого правила?

Enlico 10.12.2020 16:14

@ Энлико Это не так. Возвращаемое значение не обязательно должно быть constexpr. Но если это так, нам разрешено использовать его как один на вызывающем сайте. Что, в свою очередь, зависит от переданного параметра, но на вызывающем сайте компилятор знает, является ли переданный параметр constexpr или нет, в отличие от определения функции.

super 10.12.2020 16:22

Но разве он не знает во время создания универсальной лямбда-функции/шаблона?

Enlico 10.12.2020 16:23

Конечно. Но даже созданный шаблон должен вызываться во время выполнения, так что это ничего не меняет.

super 10.12.2020 16:24

Наверное, я пропускаю какие-то моменты. Я не понимаю, почему компилятор не может создать экземпляр лямбды с auto равным std::array<some_type, 3>, а затем также вызвать его во время выполнения. Это std::array, он не изменит размер во время выполнения, не так ли?

Enlico 10.12.2020 16:27

@Enlico Я уверен, что здесь, на SO, есть дубликаты, в которых более подробно рассказывается об этом, но я не могу найти их прямо сейчас. Я не могу дать вам вразумительный ответ на этот вопрос, но я на 99% уверен, что где-то видел и читал его здесь.

super 10.12.2020 16:37

@super, я вернулся к этому, когда смотрел презентацию, которую я сейчас связал с ответом на вопрос, который я только что написал. Теперь я понимаю, что вы имели в виду. Большое спасибо. Кстати, это то видео, которое вы имели в виду в своем втором комментарии?

Enlico 15.01.2021 19:48
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
14
688
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Проблема в [expr.const]/5.12:

5 - Выражение E является основным константным выражением, если только вычисление E, следуя правилам абстрактной машины ([intro.execution]), не будет оценивать одно из следующего: [...]

  • (5.12) id-выражение, которое ссылается на переменную или член данных ссылочного типа, если ссылка не имеет предшествующей инициализации и либо
    • (5.12.1) его можно использовать в постоянных выражениях или
    • (5.12.2) его время жизни началось при оценке E;

Поскольку переменная array является ссылкой, ее нельзя вычислять (внутри выражения array.size()), даже если вычисление фактически ничего не делает.

Передача array по значению (const или не const) делает код действительным:

constexpr auto print_size = [](auto const array){
    constexpr auto size = array.size(); // ok
    std::cout << size << '\n';
};

Но использование ссылки на этот параметр в следующей строке недопустимо:

constexpr auto print_size = [](auto const arr){
    auto const& array = arr;
    constexpr auto size = array.size(); // error
    std::cout << size << '\n';
};

Обратите внимание, что gcc 9 неправильно принимает этот код; только с версии 10 gcc понимает это правильно.

gcc 10 по-прежнему не соответствует требованиям в соответствующей области; он принимает вызов функции-члена static constexpr для ссылки. Использование статического члена ссылки constexpr в качестве аргумента шаблона Это неверно, и clang правильно отклоняет его:

struct S { static constexpr int g() { return 1; } };
void f(auto const& s) {
    constexpr auto x = s.g(); // error
    constexpr auto y = decltype(s)::g(); // ok
}
int main() { f(S{}); }

Приложение: это может измениться в будущем, согласно документу P2280R1.

Я не уверен, что этот ответ правильный. Параметры функции constexpr отсутствуют, поэтому constexpr auto size = array.size(); должно быть недопустимым во всех случаях. Я думаю, что причина, по которой он компилируется, заключается в том, что operator() является шаблоном (из-за auto), а недопустимый шаблон constexpr не требует никакой диагностики.

NathanOliver 10.12.2020 18:27

@NathanOliver хорошо, clang принимает функцию (не шаблон) void print_size(std::array<int, 3> const array): godbolt.org/z/zKbYvs

ecatmur 10.12.2020 18:38

@NathanOliver Я не понимаю, как array.size(), вызванный для (не-constexpr) значения типа std::array<int, 3>, нарушает какое-либо положение eel.is/c++draft/expr.const#5?

ecatmur 10.12.2020 18:40

Моя проблема заключается в том, что переменная, не являющаяся constexpr, которой должна быть array, не должна возвращать значение constexpr, в котором инициализируется size. Я не понимаю, как это позволяет его использовать.

NathanOliver 10.12.2020 19:01

@NathanOliver array::size() — это constexpr функция, которая не имеет доступа ни к каким членам данных array, и компилятор может это видеть. Мне кажется, это нормально.

ecatmur 10.12.2020 19:16
Ответ принят как подходящий

Я смотрел презентацию 2014 Metaprogramming with Boost.Hana: Unifying Boost.Fusion и Boost.MPL, где Луиза Дион затрагивает эту тему и объясняет, что @super говорит мне в комментариях, но я не понимаю.

Это моя переформулировка этой концепции: не существует такой вещи, как параметр функции constexpr, поэтому всякий раз, когда лямбда (фактически лежащая в ее основе operator()) создается для данного типа array, это единственное создание должно работать как для constexpr и не-constexpr аргументы этого типа.

Как говорит Луи Дионн в связанной презентации,

[…] вы не можете сгенерировать constexpr внутри функции, если она зависит от параметра […] тип возвращаемого значения функции может зависеть только от типов ее аргументов, а не от их значений […]

Это дает способ обойти проблему. Используйте тип array без использования значения array:

constexpr auto print_size = [](auto const& array){
    using array_type = decltype(array);
    constexpr auto size = array_type{}.size();
    std::cout << size << '\n';
};

что, я думаю, по сути ничем не отличается от того, что @Jarod42 предложил в комментарии:

Вы можете использовать constexpr auto size = std::tuple_size<std::decay_t<decltype(array)>>::value

В дополнение, я еще немного поигрался, потому что меня беспокоило последнее: размер std::array не является частью значения, но является частью типа, так почему я не могу вызвать size функцию-член в contexprs? ? Причина в том, что std::array<T,N>::size(), к сожалению, не является статическим. Если бы это было так, его можно было бы назвать так, как в строке комментариев ниже (struct A для сравнения):

#include <array>
#include <iostream>
#include <type_traits>

template<std::size_t N>
struct A {
    static constexpr std::size_t size() noexcept { return N; }
};
constexpr auto print_size = [](auto const& array){
    constexpr auto size = std::decay_t<decltype(array)>::size();
    std::cout << size << '\n';
};

int main() {
    //print_size(std::array<int,3>{{1,2,3}});
    print_size(A<3>{});
}

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