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ć.


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


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.


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

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

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.