Блог пользователя goo.gl_SsAhv

Автор goo.gl_SsAhv, 12 лет назад, По-русски

Если я не ошибаюсь, когда-то давно видел код c темплейтами, который во время компиляции выполнял алгоритм решета эратосфена на массиве константной длины. Можете подсказать как подобное делается или поделиться ссылкой? Было бы еще лучше инициализировать константный массив подобным образом, типа statiс const int arr[42]; на самом деле задача проще, нужно проинциализировать счетчик числа битов a[i] = a[i >> 1] + (i & 1)

  • Проголосовать: нравится
  • +4
  • Проголосовать: не нравится

»
12 лет назад, # |
  Проголосовать: нравится +6 Проголосовать: не нравится

Это называется шаблонное метапрограммирование. Примеры кода можно найти тут.

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +2 Проголосовать: не нравится

    "Шаблонне метапрограмування". Спасибо. Но я уже рассмотрел несколько нагугленных примеров, даже инициализирующих массив, но либо криво, либо не константный.

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      Массив инициализировать вряд ли получится, тогда уж можно просто макросами писать. То, что получится — это вместо массива сделать шаблон, параметризуемый int, и потом специализировать его для нужных значений. Тогда обращение к «массиву» будет выглядеть как my_array<42>::value().

      • »
        »
        »
        »
        12 лет назад, # ^ |
          Проголосовать: нравится +1 Проголосовать: не нравится

        вот кривой пример

        template<int n>
        struct CC {
            CC<n - 1> prev;
            int x;
            CC() : x(n) {}
        };
        
        template<>
        struct CC<0> {
            int x;
            CC<0>(): x(0) {}
        };
        
        const int N = 10;
        const CC<N> c;
        
        const int* a = reinterpret_cast<const int*>(&c);
        
        
        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
            Проголосовать: нравится 0 Проголосовать: не нравится

          Ага, я это видел в гугле. Только вот преобразование const CC<N> * -> const int * и последующее разыменование этого указателя вызывает undefined behavior. Такое прокатит только если вместо int взять char/unsigned char, да и тогда не факт, что будет работать правильно.

          • »
            »
            »
            »
            »
            »
            12 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится

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

»
12 лет назад, # |
  Проголосовать: нравится +25 Проголосовать: не нравится

offtop: в последней scala (2.10) ввели механизм макросов, с помощью которого можно выполнять на этапе компиляции практически любой код

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +8 Проголосовать: не нравится

    Хмм, а на codeforces ведь есть скала, только более старая... В связи с этим интересно было бы услышать time-limit/memory-limit для компиляции :)

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится +16 Проголосовать: не нравится

      В связи с этим интересно было бы услышать когда обновят скалу :)

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Ошибка выполнения [COMPILATION_ERROR] 
      Can't compile program.cpp:
      
      cc1plus.exe: out of memory allocating 794465653 bytes
      

      Это я смог получить в запуске. Какая-то странная константа.

      • »
        »
        »
        »
        12 лет назад, # ^ |
          Проголосовать: нравится +8 Проголосовать: не нравится

        Я думаю это то, сколько ты запросил, а не сколько разрешено, так что ничего странного

»
12 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

И аналогичный вопрос — с языком D.

»
12 лет назад, # |
  Проголосовать: нравится +9 Проголосовать: не нравится

Когда я разбирался с возможностями C++, у меня получилось вот это: examples

Конкретно код, который во время компиляции считает все простые до 403: primes

P.S. На практике возможности предподсчета на C++ во время компиляции не применимы. Слишком медленно работает.

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +48 Проголосовать: не нравится

    Когда-то тоже с этим разбирался и нашел одно полезное применение.

    Когда мы пишем быстрое преобразование Фурье, то самым лучшим алгоритмом по критерию простота написания/скорость работы является рекурсивный вариант вида:

    void Step(base* data, int N) {
      if (N == 1) return;
      Step(data, N / 2);
      Step(data + N / 2, N / 2);
      base w(cos(2. * pi / N), sin(2. * pi / N));
      base cur(1), buf;
      for (int i = 0; i < N / 2; ++i, cur *= w) {
        buf = cur * data[i + N / 2];
        data[i + N / 2] = data[i] - buf;
        data[i] += buf;
      }
    }
    

    На всем известной задаче решение с таким написанием Фурье проходит более чем с двукратным запасом по тайм-лимиту.

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

    template<unsigned N>
    void Step(base* data) {
      Step<N / 2>(data);
      Step<N / 2>(data + N / 2);
    
      base w(cos(2. * pi / N), sin(2. * pi / N));
      base cur(1), buf;
      for (int i = 0; i < N / 2; ++i, cur *= w) {
        buf = cur * data[i + N / 2];
        data[i + N / 2] = data[i] - buf;
        data[i] += buf;
      }
    }
    
    template<>
    void Step<1>(base* data) {
    }
    

    При такой реализации функцию нужно вызывать следующим образом:

    Step<1 << 18>(our_array)
    

    Естественно, минус такого подхода в том, что мы заранее должны знать длину массива, к которому применять функцию. Однако, если в задаче ограничения известны заранее и нет мультитеста — можно всегда вызывать с максимально возможной длиной. Если же есть мультитест, то не обойтись без switch, или кучи ифов :)

    Такое Фурье по-прежнему совсем несложно писать, но работает оно действительно существенно быстрее.

    • »
      »
      »
      12 лет назад, # ^ |
      Rev. 5   Проголосовать: нравится +34 Проголосовать: не нравится

      Действительно классно.

      Жаль, что я уже нерекурсивно пишу. Это после Харьковского контеста на Фурье от Димы Жукова :-) Получается вроде тоже достаточно коротко: fourier

      P.S. Интересно было бы такую же идею применить, например, к дереву отрезков с запросом сверху (хотя бы для малых N <= 2^10).

      UPD: Попробовал переписать "дерево отрезков с операциями sum и +=" на шаблоны для N <= 2^8. Компилится секунду. Зато стало работать в (!) 5 раз быстрее. Круто. Вот код: старый новый.

      UPD2: "В 5 раз быстрее" достигается при использовании MinGW g++ 4.7.2 на core i5. При использовании Visual C++ 2005 и 2010 Express у меня получилось только "в 1.8 раз быстрее"

      • »
        »
        »
        »
        12 лет назад, # ^ |
          Проголосовать: нравится +5 Проголосовать: не нравится

        Ну в качестве совсем жесткого пихания можно так раскрывать сколько-то верхних уровней. Или наоборот нижних.

»
12 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

А здесь BrainFuck на шаблонах, что заодно доказывает тьюринг-полноту :)

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    С тьюринг-полнотой все плохо из-за наличия в компиляторах жесткого ограничения на глубину раскрытия :)

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится +5 Проголосовать: не нравится

      А как же -ftemplate-depth=n в GCC? :-) GCC, пожалуй, ограничен только доступным количеством памяти.

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится +5 Проголосовать: не нравится

      Ну, с Тьюринг-полнотой, наверное, везде все плохо из-за отсутвия бесконечной памяти. Просто тут на практике предел слишком легко достигается.

»
12 лет назад, # |
  Проголосовать: нравится +10 Проголосовать: не нравится

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

Пример: решение задачи 237C с вычислением количеств простых чисел во время выполнения и во время компиляции. Разница в двух строчках: объявим мы эти массивы как глобальные константы (immutable) или как переменные на стеке.

Конечно, это только в теории всё отлично — на практике есть несколько проблем. Из того, что я уже видел: интерпретатор, который выполняет compile-time evaluation, на пару порядков медленнее и более неуклюже работает, чем скомпилированный вариант функции. Так что ресурсоёмкий предподсчёт компилятору не скормить — у него быстро кончается время или память. Ну и конкретно в этой задаче, конечно, от вычисления таблицы простых на этапе компиляции (которое, кстати, занимает порядка десяти секунд!) решение быстрее не стало (фактически стало даже медленнее: 125 ms против 109 ms, но это вполне вписывается в погрешность при измерении времени).