Время компиляции и условия - PullRequest
9 голосов
/ 06 января 2012

Я читал ответы на "Печать от 1 до 1000 без цикла или условных выражений" , и мне интересно, почему в верхнем ответе должен быть специальный случай для 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);
}

Ответы [ 8 ]

12 голосов
/ 06 января 2012

Генерация и компиляция кода не ветвятся в зависимости от условий!Учтите это:

// don't declare bar()!

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

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

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

template <> struct NumberGeneration<0> { /* no more recursion here */ };
4 голосов
/ 06 января 2012

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

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

3 голосов
/ 06 января 2012

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

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

1 голос
/ 06 января 2012

Я вполне уверен, что это зависит от компилятора; некоторые компиляторы могут попытаться сгенерировать обе ветви 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, которая ничего не делает).

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

1 голос
/ 06 января 2012

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

1 голос
/ 06 января 2012

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

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

0 голосов
/ 06 января 2012

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

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);
}

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

0 голосов
/ 06 января 2012

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

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

...