Jak mieć stałą const w pętli for do generowania klas szablonów?

15

Mam taki kod

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

Teraz chcę utworzyć instancje klasy i wywołać w niej funkcje w pętli for dla zestawu wielu wartości, takich jak

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

Jak to zrobić? Mając nadzieję na metodę, aby to zrobić.

nachiappan venkatesh
źródło
Być stosowane jako parametr szablonu Nmusi być constexprktóra jeśli jest to zmienna pętla że nie jest to przypadek
CoryKramer
Nie możesz, czy A naprawdę musi być szablonem?
Alan Birtles,
Tak, z pewnych powodów klasa A musi być szablonem i jest to model czegoś, więc musi to być klasa szablonów
nachiappan venkatesh

Odpowiedzi:

11

Wymagałoby to czegoś, co nazywa się template foroczekiwaną formą ekspansji , która wygląda jak pętla for, ale w rzeczywistości jest blokiem szablonowym w funkcji, która jest wielokrotnie instalowana.

Oczywiście istnieje obejście tego problemu. Możemy nadużywać ogólnych lambdów, aby zadeklarować jakiś lokalny blok szablonów i sami go wprowadzić:

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>{})), ...);
}

Ta funkcja przyjmuje sekwencję całkowitą i tworzy instancję lambda Ftyle razy, ile długość sekwencji.

Używa się go w następujący sposób:

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

Tutaj Nmożna wysłać jako parametr szablonu, ponieważ jest to obiekt, który ma operator konwersji constexpr na typ całkowity. Mówiąc dokładniej, std::integral_constantma coraz większą wartość.

Przykład na żywo

Racilla Guillaume
źródło
3
Ugh. Kiedy widzę taki szablon zabawy, po prostu wiem, że będę musiał go później debugować bez callstacka i muszę zgadnąć, co się dzieje ... :)
Michael Dorgan
Jaki jest cel static_cast<void>?
Ayxan,
2
@Ayxan unika problemów, gdy lambda fzwraca typ, który przeciąża przecinek
Guillaume Racicot
@MichaelDorgan Właśnie dlatego potrzebujemy template for. Nadużywanie takich konstrukcji językowych jest zawsze bardziej bolesne
Guillaume Racicot,
@GuillaumeRacicot lub potrzebujemy lepszych abstrakcji niż szablonów do meta programowania.
Ajay Brahmakshatriya,
5

W Nmusi być stała podczas kompilacji, który jest w normalnym forpętli nie jest możliwe.

Istnieje jednak wiele obejść. Na przykład, zainspirowany tym postem SO , możesz zrobić coś takiego: ( Zobacz prezentację na żywo )

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

Drukuje 1do100


W powyższe można sprowadzić do jednej AGeneratorklasy szablonów (tzn. Można uniknąć specjalizacji), używając if constexpr. ( Zobacz prezentację na żywo )

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

Wyjście :

1
2
3
4
5
6
7
8
9
10

W przypadku podania zakresu iteracji można użyć następujących opcji. ( Zobacz prezentację na żywo )

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

Dane wyjściowe są takie same jak w powyższej wersji.

JeJo
źródło
4

Z C ++ 20 możesz używać szablonów lambdas, więc możesz wypróbować coś w następujący sposób

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

Poniżej znajduje się pełny przykład kompilacji, w którym drukowane są wszystkie liczby od 0 do 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
źródło
1

Jednym ze sposobów, w jaki można to zrobić, jest meta-programowanie szablonów z czymś takim:

#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
źródło
0

Po prostu kompletność - czy naprawdę jest wymagana, aby klasa lub funkcja była szablonowana, jeśli jedyne użycie funkcji ma być wywołane z pętli?

Jeśli tak, a nie chcesz pisać ręcznie, spójrz na boost.hana.

CapSel
źródło