Как иметь переменную const в цикле for для генерации шаблонных классов?

15

У меня есть код как

template <size_t N>
class A
{
    template <size_t N>
    someFunctions() {};
};

Теперь я хочу создать экземпляры класса и вызвать его функции в цикле for для множества значений:

// in main()

int main()
{
    for (int i = 1; i <= 100; i++)
    {
        const int N = i;  // dont know how to do this
        A<N> a;
        a.functionCalls();
    }
}

Как это сделать? Надеясь на метод, чтобы сделать это.

Начиаппан Венкатеш
источник
Для использования в качестве параметра шаблона Nнеобходимоconstexpr если это переменная цикла, это не так
CoryKramer
Вы не можете, действительно ли А должен быть шаблоном?
Алан
Да, по некоторым причинам необходимо, чтобы класс А был шаблоном, и это образец чего-то, поэтому он должен быть шаблоном класса
nachiappan venkatesh

Ответы:

11

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

Конечно, есть обходной путь. Мы можем использовать общие лямбды, чтобы объявить какой-то локальный шаблонный блок и создать его сами:

template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F f) {
    (static_cast<void>(f(std::integral_constant<T, S>{})), ...);
}

Эта функция принимает целочисленную последовательность и создает лямбду Fстолько раз, сколько длина последовательности.

Используется так:

for_sequence(std::make_index_sequence<100>(), [](auto N) { /* N is from 0 to 99 */
  A<N + 1> a; /* N + 1 is from 1 to 100 */
  a.functionCalls();
});

Здесь Nможно отправить как параметр шаблона, потому что это объект, у которого есть оператор преобразования constexpr в целочисленный тип. Точнее, это std::integral_constantс возрастающей ценностью.

Живой пример

Гийом Расико
источник
3
Тьфу. Когда я вижу шаблон забавного, как это, я просто знаю, что мне придется отлаживать его позже без стека вызовов, и я должен угадать, что происходит ... :)
Майкл Дорган
Какова цель static_cast<void>?
Ayxan
2
@Ayxan позволяет избежать проблем, когда лямбда-выражение fвозвращает тип, который перегружает оператор запятой
Гийом
@MichaelDorgan Вот почему нам нужно template for. Злоупотребление такими языковыми конструкциями всегда более болезненно
Гийом Расикот
@GuillaumeRacicot или нам нужны лучшие абстракции, чем шаблоны для метапрограммирования.
Аджай Брахмакшатрия
5

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

Но есть много обходных путей. Например, вдохновленный этим SO сообщением , вы можете сделать что-то вроде следующего. ( См. Живую демоверсию )

template<size_t N>
class A
{
public:
    // make the member function public so that you can call with its instance
    void someFunctions()
    {
        std::cout << N << "\n";
    };
};

template<int N> struct AGenerator
{
    static void generate()
    {
        AGenerator<N - 1>::generate();
        A<N> a;
        a.someFunctions();
    }
};

template<> struct AGenerator<1>
{
    static void generate()
    {
        A<1> a;
        a.someFunctions();
    }
};

int main()
{
    // call the static member for constructing 100 A objects
    AGenerator<100>::generate();
}

Печать 1в100


В вышеприведенное может быть сведено к одному шаблонному AGeneratorклассу (то есть можно избежать специализации), используя if constexpr. ( См. Живую демонстрацию )

template<std::size_t N>
struct AGenerator final
{
    static constexpr void generate() noexcept
    {
        if constexpr (N == 1)
        {
            A<N> a;
            a.someFunctions();
            // .. do something more with `a`
        }
        else
        {
            AGenerator<N - 1>::generate();
            A<N> a;
            a.someFunctions();
            // .. do something more with `a`
        }
    }
};

Выход :

1
2
3
4
5
6
7
8
9
10

В случае предоставления диапазона итерации, вы можете использовать следующее. ( См. Живую демонстрацию )

template<std::size_t MAX, std::size_t MIN = 1> // `MIN` is set to 1 by default
struct AGenerator final
{
    static constexpr void generate() noexcept
    {
        if constexpr (MIN == 1)
        {
            A<MIN> a;
            a.someFunctions();
            // .. do something more with `a`
            AGenerator<MAX, MIN + 1>::generate();
        }
        else if constexpr (MIN != 1 && MIN <= MAX)
        {
            A<MIN> a;
            a.someFunctions();
            // .. do something more with `a`
            AGenerator<MAX, MIN + 1>::generate();
        }
    }
};

int main()
{
    // provide the `MAX` count of looping. `MIN` is set to 1 by default
    AGenerator<10>::generate();
}

Выходы такие же, как в вышеприведенной версии.

JeJo
источник
4

Начиная с C ++ 20, вы можете использовать лямбды-шаблоны, поэтому вы можете попробовать что-то вроде следующего

[]<int ... Is>(std::integer_sequence<int, Is...>)
 { (A<Is+1>{}.functionCall(), ...); }
   (std::make_integer_sequence<int, 100>{});

Ниже приведен полный пример компиляции, который печатает все числа от 0 до 99

#include <utility>
#include <iostream>

int main()
 {
  []<int ... Is>(std::integer_sequence<int, Is...>)
   { (std::cout << Is << std::endl, ...); }
     (std::make_integer_sequence<int, 100>{});
 }
max66
источник
1

Один из способов сделать это - шаблонное метапрограммирование с чем-то вроде этого:

#include <iostream>

template <std::size_t N>
struct A {
  void foo() { std::cout << N << '\n'; }
};

template <std::size_t from, std::size_t to>
struct call_foo {
  void operator()() {
    if constexpr (from != to) {
      A<from + 1>{}.foo();
      call_foo<from + 1, to>{}();
    }
  }
};

int main() { call_foo<0, 100>{}(); }
Ayxan
источник
0

Просто для полноты - действительно ли требуется, чтобы класс или функция были шаблонными, если единственное использование функции вызывается из цикла?

Если это так, и вы не хотите писать от руки, взгляните на boost.hana.

CapSel
источник