πάντα ῥεῖ дал хороший и полезный ответ, я хотел бы чтобы упомянуть еще один вопрос, хотя с constexpr for
.
В C++ на самом фундаментальном уровне все выражения имеют тип, который можно определить статически (во время компиляции). Конечно, есть такие вещи, как RTTI и boost::any
, но они построены поверх этой структуры, а статический тип выражения является важной концепцией для понимания некоторых правил в стандарте.
Предположим, что вы можете перебрать гетерогенную контейнер с помощью фантазии синтаксиса, как это может быть:
std::tuple<int, float, std::string> my_tuple;
for (const auto & x : my_tuple) {
f(x);
}
Здесь f
некоторая перегруженная функция. Ясно, что предполагаемый смысл этого заключается в вызове различных перегрузок f
для каждого из типов в кортеже. Это действительно означает, что в выражении f(x)
разрешение перегрузки должно выполняться три раза.Если мы будем играть по текущим правилам C++, единственный способ, который это может иметь смысл, - это, если мы в основном разворачиваем цикл в три разных тела цикла, до, мы пытаемся выяснить, каковы типы выражений.
Что делать, если код на самом деле
for (const auto & x : my_tuple) {
auto y = f(x);
}
auto
не магия, это не означает, что «нет информации типа», это означает, что «вывести тип, пожалуйста, составитель». Но ясно, что действительно необходимо иметь три разных типа: y
.
С другой стороны, в этом случае есть сложные проблемы - в C++ анализатор должен знать, какие имена являются типами и какие имена являются шаблонами, чтобы правильно разобрать язык. Может ли анализатор быть изменен, чтобы сделать несколько циклов для разворачивания циклов constexpr for
до того, как все типы будут разрешены? Я не знаю, но я думаю, что это может быть нетривиально. Может быть, есть лучший способ ...
Чтобы избежать этой проблемы, в текущих версиях C++ люди используют шаблон посетителя. Идея состоит в том, что у вас будет перегруженная функция или объект функции, и она будет применена к каждому элементу последовательности. Тогда каждая перегрузка имеет свое «тело», поэтому нет никакой двусмысленности в отношении типов или значений переменных в них. Существуют библиотеки, такие как boost::fusion
или boost::hana
, которые позволяют выполнять итерацию по гетерогенным последовательностям с использованием заданного vistior - вы должны использовать их механизм вместо цикла for.
Если вы могли бы сделать constexpr for
только с ints, например.
for (constexpr i = 0; i < 10; ++i) { ... }
Это создает ту же сложность, что и гетерогенная петля. Если вы можете использовать i
в качестве параметра шаблона внутри тела, то вы можете создавать переменные, относящиеся к разным типам, в разных циклах тела цикла, а затем неясно, какими должны быть статические типы выражений.
Итак, я не уверен, но я думаю, что могут быть некоторые нетривиальные технические проблемы, связанные с фактическим добавлением функции constexpr for
к языку. Шаблон посетителя/планируемые функции отражения может оказаться меньше головной боли ИМО ... кто знает.
Позвольте мне привести еще один пример, о котором я только подумал, показывает сложность.
В обычном C++ компилятор знает статический тип каждой переменной в стеке и поэтому может вычислять макет фрейма стека для этой функции.
Вы можете быть уверены, что адрес локальной переменной не будет изменяться во время выполнения функции. Например,
std::array<int, 3> a{{1,2,3}};
for (int i = 0; i < 3; ++i) {
auto x = a[i];
int y = 15;
std::cout << &y << std::endl;
}
В этом коде y
является локальной переменной в теле для цикла. Он имеет четко определенный адрес во всей этой функции, а адрес, напечатанный компилятором, будет одинаковым каждый раз.
Каким должно быть поведение подобного кода с constexpr?
std::tuple<int, long double, std::string> a{};
for (int i = 0; i < 3; ++i) {
auto x = std::get<i>(a);
int y = 15;
std::cout << &y << std::endl;
}
Дело в том, что тип x
выводится по-разному в каждом проходе через петлю - так как он имеет другой тип, он может иметь различный размер и выравнивание в стеке. Так как y
приходит после него в стек, это означает, что y
может изменить свой адрес на разных прогонах цикла - правильно?
Какое должно быть поведение, если указатель на y
берется за один проход через цикл, а затем разыменован в более позднем проходе? Должно ли быть неопределенное поведение, хотя это, вероятно, было бы законным в аналогичном коде «no-constexpr for» с std::array
, показанным выше?
Нельзя ли изменить адрес y
? Должен ли компилятор заполнить адрес y
, чтобы можно было разместить самый большой из типов в кортеже до y
? Означает ли это, что компилятор не может просто развернуть циклы и начать генерировать код, но должен развернуть каждый экземпляр цикла раньше, а затем собрать всю информацию о типе из каждого из N
экземпляров, а затем найти удовлетворительный макет?
Я думаю, что вам лучше просто использовать расширение пакета, гораздо более ясно, как он должен реализовываться компилятором и насколько он эффективен при компиляции и времени выполнения.
Err ... для петель не бывает во время компиляции. Они должны быть отправлены в исполняемый файл компилятором, где они затем выполняются во время *** запуска ***. –
В целом, для циклов нельзя компилировать-время. Вы говорите о очень конкретном случае. Но тот же аргумент можно было бы использовать практически для любой конструкции, а не только для циклов. –
Ну, циклы могут быть _kinda_ выражением времени компиляции, когда компилятор решает развернуть цикл – ForceBru