Ответ 1
πάντα ῥεῖ дал хороший и полезный ответ, хотелось бы упомянуть еще один вопрос, но с constexpr for
.
В С++ на самом фундаментальном уровне все выражения имеют тип, который можно определить статически (во время компиляции). Конечно, есть такие вещи, как RTTI и boost::any
, но они построены поверх этой структуры, а статический тип выражения является важной концепцией для понимания некоторых правил в стандарте.
Предположим, что вы можете перебирать гетерогенный контейнер с использованием синтаксиса для синтаксиса, например:
std::tuple<int, float, std::string> my_tuple;
for (const auto & x : my_tuple) {
f(x);
}
Здесь f
- некоторая перегруженная функция. Понятно, что предполагаемое значение этого метода заключается в вызове различных перегрузок f
для каждого из типов в кортеже. Это действительно означает, что в выражении f(x)
разрешение перегрузки должно выполняться три раза. Если мы будем играть по текущим правилам С++, единственный способ это может иметь смысл - если мы в основном разворачиваем цикл в три разных тела цикла, прежде чем мы попытаемся выяснить, каковы типы выражений.
Что делать, если на самом деле код
for (const auto & x : my_tuple) {
auto y = f(x);
}
auto
не волшебство, это не означает "нет информации типа", это означает "вывести тип, пожалуйста, компилятор". Но ясно, что в общем случае действительно должны быть три разных типа y
.
С другой стороны, есть сложные проблемы с подобным делом - в С++ парсер должен знать, какие имена являются типами и какие имена являются шаблонами, чтобы правильно разобрать язык. Можно ли изменить синтаксический анализатор для выполнения циклического цикла циклов constexpr for
до того, как все типы будут разрешены? Я не знаю, но я думаю, что это может быть нетривиально. Может быть, есть лучший способ...
Чтобы избежать этой проблемы, в текущих версиях С++ люди используют шаблон посетителя. Идея состоит в том, что у вас будет перегруженная функция или объект функции, и она будет применена к каждому элементу последовательности. Тогда каждая перегрузка имеет свое "тело", поэтому нет никакой двусмысленности в отношении типов или значений переменных в них. Существуют библиотеки типа boost::fusion
или boost::hana
, которые позволяют выполнять итерацию по гетерогенным последовательностям с использованием заданного vistior - вы должны использовать свой механизм вместо цикла for.
Если вы можете сделать constexpr for
только с помощью ints, например
for (constexpr i = 0; i < 10; ++i) { ... }
возникает такая же сложность, как и гетерогенная для цикла. Если вы можете использовать i
в качестве параметра шаблона внутри тела, тогда вы можете создавать переменные, которые относятся к разным типам в разных прогонах тела цикла, а затем не ясно, какими должны быть статические типы выражений.
Итак, я не уверен, но я думаю, что могут быть некоторые нетривиальные технические проблемы, связанные с фактическим добавлением функции constexpr for
к языку. Шаблон посетителя/планируемые функции отражения могут оказаться меньше головной боли ИМО... кто знает.
Позвольте мне привести еще один пример, который я только что подумал о том, что показывает трудность.
В обычном С++ компилятор знает статический тип каждой переменной в стеке и поэтому может вычислить макет фрейма стека для этой функции.
Вы можете быть уверены, что адрес локальной переменной не будет изменяться во время выполнения функции. Например,
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
является локальной переменной в теле цикла for. Он имеет четко определенный адрес во всей этой функции, а адрес, напечатанный компилятором, будет одинаковым каждый раз.
Каким должно быть поведение аналогичного кода с 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
принимается за один проход через цикл, а затем разыменован в более позднем проходе? Должно ли быть поведение undefined, хотя оно, вероятно, было бы законным в аналогичном коде "no-constexpr for" с std::array
, показанным выше?
Нельзя ли изменить адрес y
? Должен ли компилятор заполнить адрес y
так, чтобы наибольший из типов в кортеже можно было разместить до y
? Означает ли это, что компилятор не может просто развернуть циклы и начать генерировать код, но должен развернуть каждый экземпляр цикла до-стороны, а затем собрать всю информацию о типе из каждого из экземпляров N
, а затем найти удовлетворительный макет?
Я думаю, вам лучше просто использовать расширение пакета, гораздо более ясно, как он должен реализовываться компилятором, и насколько он эффективен при компиляции и времени выполнения.