(Относится к этому другому моему вопросу; если вы тоже посмотрите на это, я был бы очень признателен.)
Если 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()
.
Что не так в моих рассуждениях?
Кстати, то же самое происходит, если я использую шаблонную функцию вместо общей лямбды.
@TedLyngmo, это делает ситуацию еще более странной. Возможно, RVO обходит какое-то ограничение языка?
Действительно. constexpr size = array.size();
внутри лямбды не будет работать, но возврат того же и присвоение переменной constexpr
работает. Понятия не имею, почему существует разница :-)
Параметры функции никогда не бывают constexpr
. Помните, что даже функцию constexpr
нужно вызывать во время выполнения.
@super Верно, но в таком случае это выглядит странно.
@TedLyngmo Действительно. Я помню, как где-то видел хорошую статью/видео об этом здесь, на SO. Это углубляется и объясняет, почему это (не уверен, что это точный сценарий, но, по крайней мере, похоже) даже невозможно с consteval
. Я посмотрю, смогу ли я найти его.
Вы можете использовать constexpr auto size = std::tuple_size<std::decay_t<decltype(array)>>::value
.
@super, как пример Теда Люнгмо избегает этого правила?
@ Энлико Это не так. Возвращаемое значение не обязательно должно быть constexpr
. Но если это так, нам разрешено использовать его как один на вызывающем сайте. Что, в свою очередь, зависит от переданного параметра, но на вызывающем сайте компилятор знает, является ли переданный параметр constexpr
или нет, в отличие от определения функции.
Но разве он не знает во время создания универсальной лямбда-функции/шаблона?
Конечно. Но даже созданный шаблон должен вызываться во время выполнения, так что это ничего не меняет.
Наверное, я пропускаю какие-то моменты. Я не понимаю, почему компилятор не может создать экземпляр лямбды с auto
равным std::array<some_type, 3>
, а затем также вызвать его во время выполнения. Это std::array
, он не изменит размер во время выполнения, не так ли?
@Enlico Я уверен, что здесь, на SO, есть дубликаты, в которых более подробно рассказывается об этом, но я не могу найти их прямо сейчас. Я не могу дать вам вразумительный ответ на этот вопрос, но я на 99% уверен, что где-то видел и читал его здесь.
@super, я вернулся к этому, когда смотрел презентацию, которую я сейчас связал с ответом на вопрос, который я только что написал. Теперь я понимаю, что вы имели в виду. Большое спасибо. Кстати, это то видео, которое вы имели в виду в своем втором комментарии?
Проблема в [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 хорошо, clang принимает функцию (не шаблон) void print_size(std::array<int, 3> const array)
: godbolt.org/z/zKbYvs
@NathanOliver Я не понимаю, как array.size()
, вызванный для (не-constexpr
) значения типа std::array<int, 3>
, нарушает какое-либо положение eel.is/c++draft/expr.const#5?
Моя проблема заключается в том, что переменная, не являющаяся constexpr, которой должна быть array
, не должна возвращать значение constexpr, в котором инициализируется size
. Я не понимаю, как это позволяет его использовать.
@NathanOliver array::size()
— это constexpr
функция, которая не имеет доступа ни к каким членам данных array
, и компилятор может это видеть. Мне кажется, это нормально.
Я смотрел презентацию 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
функцию-член в contexpr
s? ? Причина в том, что 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>{});
}