Wie kann eine const-Variable in einer for-Schleife zum Generieren von Vorlagenklassen verwendet werden?

15

Ich habe einen Code wie

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

Jetzt möchte ich Instanzen der Klasse erstellen und die darin enthaltenen Funktionen in einer for-Schleife für eine Reihe von Werten wie aufrufen

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

Wie macht man das? Ich hoffe auf eine Methode, um dies zu tun.

Nachiappan Venkatesh
quelle
Um als Vorlagenparameter verwendet zu werden, Nmuss dies constexprder Fall sein , wenn es sich um eine Schleifenvariable handelt, die nicht der Fall ist
CoryKramer
Sie können nicht, muss A wirklich eine Vorlage sein?
Alan Birtles
Ja, aus bestimmten Gründen muss die Klasse A eine Vorlage sein, und es ist ein Modell für etwas, also muss es eine Vorlagenklasse sein
nachiappan venkatesh

Antworten:

11

Dies würde etwas erfordern, das als a bezeichnet template forwird. Dies ist die erwartete Form, die Erweiterungsanweisungen annehmen. Dies sieht aus wie eine for-Schleife, ist jedoch in Wirklichkeit ein Vorlagenblock in einer Funktion, die mehrfach instanziiert wird.

Natürlich gibt es eine Problemumgehung. Wir können generische Lambdas missbrauchen, um eine Art lokalen Vorlagenblock zu deklarieren und ihn selbst zu instanziieren:

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

Diese Funktion nimmt eine ganzzahlige Sequenz und instanziiert das Lambda Fso oft wie die Länge der Sequenz.

Es wird so verwendet:

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

Hier Nkann als Vorlagenparameter gesendet werden, da es sich um ein Objekt handelt, das einen Constexpr-Konvertierungsoperator in einen Integer-Typ hat. Genauer gesagt, es ist ein std::integral_constantmit zunehmendem Wert.

Live Beispiel

Guillaume Racicot
quelle
3
Pfui. Wenn ich sehe, dass eine solche Vorlage Spaß macht, weiß ich nur, dass ich sie später ohne Callstack debuggen muss und raten muss, was los ist ... :)
Michael Dorgan
Was ist der Zweck von static_cast<void>?
Ayxan
2
@ Ayxan vermeiden Sie Probleme, wenn das Lambda feinen Typ zurückgibt, der den Kommaoperator überlastet
Guillaume Racicot
@ MichaelDorgan Deshalb brauchen wir template for. Der Missbrauch solcher Sprachkonstrukte ist immer schmerzhafter
Guillaume Racicot
@ GuillaumeRacicot oder wir brauchen bessere Abstraktionen als Vorlagen für die Metaprogrammierung.
Ajay Brahmakshatriya
5

Die NKompilierzeit muss konstant sein, was bei einer normalen forSchleife nicht möglich ist.

Es gibt jedoch viele Problemumgehungen. Inspiriert von diesem SO-Beitrag können Sie beispielsweise Folgendes tun. ( Siehe eine Live-Demo )

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

Druckt 1auf100


In kann das Obige AGeneratormithilfe von auf eine einzelne Vorlagenklasse reduziert werden (dh eine Spezialisierung kann vermieden werden) if constexpr. ( Siehe eine Live-Demo )

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

Ausgabe :

1
2
3
4
5
6
7
8
9
10

Wenn Sie den Iterationsbereich bereitstellen möchten, können Sie Folgendes verwenden. ( Siehe eine Live-Demo )

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

Gibt die gleiche Ausgabe wie in der obigen Version aus.

JeJo
quelle
4

Ab C ++ 20 können Sie Vorlagen-Lambdas verwenden, sodass Sie Folgendes ausprobieren können

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

Das Folgende ist ein vollständiges Kompilierungsbeispiel, das alle Zahlen von 0 bis 99 druckt

#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
quelle
1

Eine Möglichkeit, dies zu tun, ist die Meta-Programmierung von Vorlagen mit etwa der folgenden Funktion:

#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
quelle
0

Nur der Vollständigkeit halber - ist es wirklich erforderlich, dass die Klasse oder Funktion als Vorlage verwendet wird, wenn die einzige Verwendung der Funktion aus der Schleife aufgerufen werden soll?

Wenn ja und Sie nicht von Hand schreiben möchten, schauen Sie sich boost.hana an.

CapSel
quelle