2012-01-06 4 views
9

Я читал ответы на "Printing 1 to 1000 without loop or conditionals", и мне интересно, зачем нужен специальный чехол для NumberGeneration < 1> в верхнем ответе.Рекурсия и условные условия компиляции

Если я удалю это и добавлю чек для N == 1 в шаблоне (код ниже), код не завершится компиляцией, «глубина создания экземпляра превышает максимум», но я не уверен, почему. Условны обрабатываются по-разному во время компиляции?

#include <iostream> 

template<int N> 
struct NumberGeneration 
{ 
    static void out(std::ostream& os) 
    { 
     if (N == 1) 
     { 
      os << 1 << std::endl; 
     } 
     else 
     { 
      NumberGeneration<N-1>::out(os); 
      os << N << std::endl; 
     } 
    } 
}; 

int main() 
{ 
    NumberGeneration<1000>::out(std::cout); 
} 

ответ

12

Создание и сборка кода не зависит от условных обозначений! Рассмотрим это:

// don't declare bar()! 

void foo() 
{ 
    if (false) { bar(); } 
} 

Если вы никогда не объявлять bar(), это ошибка компиляции даже если внутренняя сфера никогда не может быть достигнута. По той же причине NumberGeneration<N-1> всегда создается, независимо от того, может быть достигнута эта ветка или нет, и у вас бесконечная рекурсия.

Действительно, статический аналог условных именно специализация шаблона:

template <> struct NumberGeneration<0> { /* no more recursion here */ }; 
+0

Спасибо, это имеет смысл, я действительно не думал о создании экземпляра компиляции и был ожидая, что это будет сделано только при достижении кода. –

+2

@ baris.m: Но есть критическая тонкость того, что вы подразумеваете под «код достигнут»: он достигается один раз во время компиляции, всегда, а затем снова * условно * во время выполнения во время выполнения программы. –

+0

В моем комментарии я говорил о том, чтобы быть достигнутым во время выполнения. Ваш ответ имеет смысл. –

0

Это потому, что целые числа могут быть отрицательными и код времени исполнения (if проверки) не остановит компилятор инстанцирования шаблона с 0, -1, -2 и т.д. компилятором может быть в состоянии получить с тем, что вы предлагаете, но что, если при создании экземпляров других шаблонов (0, -1, ...) есть побочные эффекты, от которых вы зависите? В этом случае компилятор не сможет создать экземпляр для вас.

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

1

Я задаюсь вопросом, почему это необходимо иметь специальный случай для NumberGeneration < 1> в верхнем ответе.

Потому что это конечное условие для рекурсивного! Без этого, как может рекурсивный конец?

+0

Что касается условного обозначения N == 1, которое я добавил в шаблон? В этом случае мы не создаем новый с N-1, поэтому я ожидаю, что рекурсия остановится. –

+0

@ baris.m: Вы не должны удалять специализацию шаблона для условия 1, поскольку это условие завершения компиляции для завершения вычисления/компиляции. – Gob00st

+1

@ baris.m: Контент, который вы добавляете, не помогает, поскольку компиляция не заботится об этом и заканчивает компиляцию во время компиляции. Добавленная проверка полезна только во время выполнения, а не время компиляции. – Gob00st

4

Условный if не будет обрабатываться во время компиляции. Он будет обрабатываться во время выполнения.

Таким образом, даже для N = 1 компилятор будет генерировать NumberGenerator < 0>, затем NumberGenerator < -1> ... бесконечно, пока не достигнет глубины реализации шаблона.

1

В общем случае условие N == 1 в вашем коде оценивается во время выполнения (хотя компилятор может его оптимизировать), а не во время компиляции. Поэтому рекурсия экземпляра экземпляра в предложении else никогда не прерывается. NumberGeneration<1>, с другой стороны, оценивается во время компиляции и, следовательно, действует как пример завершения этого рекурсивного шаблона.

1

Я уверен, что это специфичный для компилятора; некоторые компиляторы могут попытаться сгенерировать обе ветви if/else независимо от значения N, и в этом случае компиляция завершится неудачно. Другие компиляторы могут оценить условие во время компиляции и только генерировать код для ветви, которая выполнена, и в этом случае компиляция будет успешной.

ОБНОВЛЕНИЕ: Или, как говорит Люк в комментариях, возможно, что компилятор должен сгенерировать обе ветви, чтобы код всегда терпел неудачу. Я не совсем уверен, что происходит, но в любом случае плохой идеей полагаться на условия времени выполнения для управления генерации кода компиляции.

Было бы лучше использовать специализацию:

template <int N> 
struct NumberGeneration 
{ 
    static void out(std::ostream & os) 
    { 
     NumberGeneration<N-1>::out(os); 
     os << N << std::endl; 
    } 
}; 

template <> 
void NumberGeneration<1>::out(std::ostream & os) 
{ 
    os << 1 << std::endl; 
} 

(или вы могли бы сократить это незначительно, а не специализирующаяся на N=0, с out функции, которая ничего не делает).

Также имейте в виду, что некоторые компиляторы могут не поддерживать глубоко resursive templates; C++ 03 предлагает минимальную поддерживаемую глубину только 17, что C++ 11 увеличивается до 1024. Вы должны проверить, что такое предел вашего компилятора.

+0

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

+2

Вам не нужно специализировать всю структуру, вы можете сделать это только для функции. –

+0

@PaulManta: Действительно, вы можете. Благодарю. –

3

Шаблоны создаются во время компиляции, особый случай шаблона предотвращает рекурсию компилятора ниже 1 при компиляции.

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

0

Вот правильный способ сделать это:

template<int N> 
struct NumberGeneration 
{ 
    static void out(std::ostream& os); 
}; 

template<int N> 
void NumberGeneration<N>::out(std::ostream& os) 
{ 
    NumberGeneration<N-1>::out(os); 
    os << N << std::endl; 
} 

template<> 
void NumberGeneration<1>::out(std::ostream& os) 
{ 
    os << 1 << std::endl; 
} 

int main() 
{ 
    NumberGeneration<20>::out(std::cout); 
} 

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

Смежные вопросы