2016-06-02 3 views
7

Если я хочу сделать что-то вроде итерации по кортежу, мне приходится прибегать к сумасшедшим метапрограммированию шаблонов и специализированным помощникам шаблонов. Например, следующая программа не будет работать:Почему выражение for-loop не является компилятором?

#include <iostream> 
#include <tuple> 
#include <utility> 

constexpr auto multiple_return_values() 
{ 
    return std::make_tuple(3, 3.14, "pi"); 
} 

template <typename T> 
constexpr void foo(T t) 
{ 
    for (auto i = 0u; i < std::tuple_size<T>::value; ++i) 
    { 
     std::get<i>(t); 
    }  
} 

int main() 
{ 
    constexpr auto ret = multiple_return_values(); 
    foo(ret); 
} 

Поскольку i не может быть const или мы не смогли бы реализовать. Но для циклов есть конструкция времени компиляции, которая может быть оценена статически. Компиляторы могут свободно его удалять, преобразовывать, складывать, разворачивать или делать все, что захотят, благодаря правилу as-if. Но тогда почему нельзя использовать контуры в constexpr? В этом коде нет ничего, что нужно было сделать в «runtime». Это оптимизация компилятора.

Я знаю, что вы могли бы поменять i внутри тела цикла, но компилятор все равно сможет его обнаружить. Пример:

// ...snip... 

template <typename T> 
constexpr int foo(T t) 
{ 
    /* Dead code */ 
    for (auto i = 0u; i < std::tuple_size<T>::value; ++i) 
    { 
    }  
    return 42; 
} 

int main() 
{ 
    constexpr auto ret = multiple_return_values(); 
    /* No error */ 
    std::array<int, foo(ret)> arr; 
} 

С std::get<>() это время компиляции конструкция, в отличие от std::cout.operator<<, я не могу понять, почему это запрещено.

+2

Err ... для петель не бывает во время компиляции. Они должны быть отправлены в исполняемый файл компилятором, где они затем выполняются во время *** запуска ***. –

+4

В целом, для циклов нельзя компилировать-время. Вы говорите о очень конкретном случае. Но тот же аргумент можно было бы использовать практически для любой конструкции, а не только для циклов. –

+0

Ну, циклы могут быть _kinda_ выражением времени компиляции, когда компилятор решает развернуть цикл – ForceBru

ответ

4

Почему не является циклом выражения времени компиляции?

Поскольку цикл for() используется для определения времени выполнения потока управления в языке C++.

Как правило, вариационные шаблоны не могут быть распакованы в операторы потока управления во время выполнения в C++.

std::get<i>(t); 

не может быть выведено во время компиляции, так как i является переменной во время выполнения.

Вместо этого используйте variadic template parameter unpacking.


Вы также можете найти этот пост полезным (если это даже не замечает дубликат, имеющий ответы на ваш вопрос):

iterate over tuple

+8

Вы просто говорите «потому что это так». Вы на самом деле не ответили на вопрос. – Veedrac

+6

@ Veedrac: но это, по сути, ответ, - это потому, что это способ определения языка. –

+1

@ Veedrac Ну, что еще, на ваш взгляд, я должен ответить? 'for()' не предназначен для компиляции временных конструкций и никогда не будет, периодом. –

6

πάντα ῥεῖ дал хороший и полезный ответ, я хотел бы чтобы упомянуть еще один вопрос, хотя с 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 экземпляров, а затем найти удовлетворительный макет?

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

+0

for определенно выполнимо в constexpr, его по стандарту – Sergei

+0

@Sergei: наоборот, то, что предлагает OP, где переменная цикла может использоваться как параметр шаблона в цикле for for, не выполняется в 'constexpr' стандарт –

+0

Я имел в виду проблему по стандарту. Не реализовано по дизайну. Возможными реализациями являются: 1) компиляция и запуск кода во время компиляции; 2) создание шаблонов для каждой итерации; – Sergei

2

Вот способ сделать это, что не нужно слишком много шаблонного, вдохновленный от http://stackoverflow.com/a/26902803/1495627:

template<std::size_t N> 
struct num { static const constexpr auto value = N; }; 

template <class F, std::size_t... Is> 
void for_(F func, std::index_sequence<Is...>) 
{ 
    using expander = int[]; 
    (void)expander{0, ((void)func(num<Is>{}), 0)...}; 
} 

template <std::size_t N, typename F> 
void for_(F func) 
{ 
    for_(func, std::make_index_sequence<N>()); 
} 

Тогда вы можете сделать:

for_<N>([&] (auto i) {  
    std::get<i.value>(t); // do stuff 
}); 
Смежные вопросы