Template in Template Funktion aufrufen, aber mit partieller Typvorgabe



  • Hallo (C++ 23 ...), ich möchte zwei Template Funktionen in einer Test Funktion gegenüberstellen. Der Test Funktion soll nur gesagt werden (via Argument... oder Parametrisierung), welche Template Funktion sie testen soll. Dabei soll die Test Funktion selber festlegen können, wie sie die zu testende Template Funktion parametrisieren will (hier soll nur mit <int>s getestet werden).

    #include <cstdint>
    #include <cmath>
    #include <chrono>
    #include <iostream>
    #include <vector>
    #include <set>
    #include <algorithm>
    #include <numeric>
    #include <random>
    
    using namespace std;
    
    template < typename E >
    void select_n_elements_a(const vector < E > & in, vector < E > & out,
                             const uint32_t n) {
    	static auto gen = default_random_engine {
    		random_device {}()
    	};
    	vector < uint32_t > indexes(in.size());
    	iota(begin(indexes), end(indexes), 0);
    	shuffle(indexes.begin(), indexes.end(), gen);
    	sort(indexes.begin(), indexes.begin() + n);
    	for (auto it = indexes.begin(); it != indexes.begin() + n; it++) {
    		out.push_back(in [ * it]);
    	}
    }
    
    template < typename E >
    void select_n_elements_b(const vector < E > & in, vector < E > & out,
                             const uint32_t n) {
    	static auto gen = default_random_engine {
    		random_device {}()
    	};
    	ranges::sample(in, back_inserter(out), n, gen);
    }
    
    // test until the first match
    template < typename F >
    void test1(F select_n_elements,
               const uint32_t n,
               const uint32_t type) {
    	vector < int > in(n);
    	iota(begin(in), end(in), 0);
    	for (int i = 0; i < in.size();) {
    		vector < int > out = {};
    		select_n_elements(in, out, type == 0 ? 1 : n - 1);
    
    		if (type == 0 && out[0] == i || type == 1 && find(out.begin(), out.end(), i) == out.end()) {
    			i++;
    		}
    	}
    }
    
    int main() {
    	const uint32_t n = 100;
    	auto start = chrono::system_clock::now();
    	test1(select_n_elements_a < int >, n, 0);
    	auto end = chrono::system_clock::now();
    	chrono::duration < double > elapsed = end - start;
    	cout << "select_n_elements_a 1. n=" << n << ":  " << elapsed.count() << endl;
    
    	start = chrono::system_clock::now();
    	test1(select_n_elements_a < int >, n, 1);
    	end = chrono::system_clock::now();
    	elapsed = end - start;
    	cout << "select_n_elements_a 2. n=" << n << ":  " << elapsed.count() << endl;
    
    	start = chrono::system_clock::now();
    	test1(select_n_elements_b < int >, n, 0);
    	end = chrono::system_clock::now();
    	elapsed = end - start;
    	cout << "select_n_elements_b 1. n=" << n << ":  " << elapsed.count() << endl;
    
    	start = chrono::system_clock::now();
    	test1(select_n_elements_b < int >, n, 1);
    	end = chrono::system_clock::now();
    	elapsed = end - start;
    	cout << "select_n_elements_b 2. n=" << n << ":  " << elapsed.count() << endl;
    }
    

    Das Problem tritt hier in Zeile 57, 63, 69 und 75 auf. Dort muss ich bei select_n_elements_a bzw. select_n_elements_b jedes Mal int bei der Initialisierung/Referenz angeben - und das ist nicht gewollt.

    Wie würde man das richtig machen? Ich glaube, ich habe schon alle SO Fragen dazu gelesen, aber noch auf keinem grünen Zweig... Könnte es sein, dass man beim Aufruf das template Keyword zusätzlich verwenden muss?

    (Bitte nicht zu sehr auf using namespace und endl; herumreiten ...)



  • @kali-hi Ich bin kein Experte was die Deduzierungsregeln betrifft. Aber, aus dem Aufruf von test1 lässt sich nicht herleiten, welchen Typ die Parameter von dem Übergebenen Funktionstemplate haben. Das hängt erst an dem Vektor der ja in test1 angelegt wird. Aber um test1 zu instantiieren muss der Parametertyp bekannt sein.

    Du kannst, wenn du nicht jedes mal das int schreiben willst, ein alias erstellen: auto fkt1 = select_n_elements_a<int>;, oder, wenn das vom Vektor Typ abhängen soll, kannst du den Vektor als Parameter in die test1 mit übergeben und dann sowas machen:

    std::vector<int> in(n);
    using ElementType = decltype(in)::value_type;
    auto fkt1 = select_n_elements_a<ElementType>;
    test1(fkt1, 0, in);
    
    ...
    
    template <typename F, typename T>
    void test1(F select_n_elements<T>,
      const uint32_t type,
      std::vector<T>& in) 
    

    Oder, wenn du deine test1 Funktion so behältst, kannst du dir mit Lambdas helfen:

     auto lambdaA = [](const auto& in,
       auto& out,
       const uint32_t n) {
         select_n_elements_a(in, out, n);
       };
    
     auto lambdaB = [](const auto& in,
       auto& out,
       const uint32_t n) {
         select_n_elements_b(in, out, n);
       };
    
     test1(lambdA, 0,  n);
    

    Dann übergibst du nämlich direkt ein callable Objekt.



  • @Schlangenmensch sagte in Template in Template Funktion aufrufen, aber mit partieller Typvorgabe:

    std::vector<int> in(n);
    using ElementType = decltype(in)::value_type;
    auto fkt1 = select_n_elements_a<ElementType>;
    test1(fkt1, 0, in);

    Das ist das Problem, nur verlagert

    @Schlangenmensch sagte in Template in Template Funktion aufrufen, aber mit partieller Typvorgabe:

    template <typename F, typename T>
    void test1(F select_n_elements<T>,
    const uint32_t type,
    std::vector<T>& in)

    Ebenfalls das Problem, nur in einer anderen Form

    @Schlangenmensch sagte in Template in Template Funktion aufrufen, aber mit partieller Typvorgabe:

    auto lambdaA = [](const auto& in,
    auto& out,
    const uint32_t n) {
    select_n_elements_a(in, out, n);
    };

    auto lambdaB = [](const auto& in,
    auto& out,
    const uint32_t n) {
    select_n_elements_b(in, out, n);
    };

    test1(lambdA, 0, n);

    Das liest sich spannend... das int kommt hier im Aufruf und in der Deklaration (bzw. Definition) nicht mehr vor... Ich denke, das könnte es sein.

    Aber
    void test1(F select_n_elements<auto >)
    -------------------------------^^^^
    (oder etwas ähnliches) wäre nicht möglich, oder?



  • template < typename F >
    void test1(F select_n_elements,
               const uint32_t n,
               const uint32_t type)
    

    Was du konzeptuell hier versuchst, ist einen function-pointer zu einer template-function zu formen. Das geht nicht.
    Was du aber machen kannst, ist aus select_n_elements_a und select_n_elements_b ein lambda zu formen.
    Das sieht dann so aus: https://godbolt.org/z/sfv5o5hqs

    Alternativ gäbe es noch andere Varianten aber diese ist mit Abstand die simpelste. Notfalls kannst du diese template Funktionen auch einfach mit einem lambda wrappen.

    Wenn es denn unbedingt irgendwas mit dieser Art placeholder sein soll, dann kann dir vll auch ein Hilfs-strukt weiterhelfen: https://godbolt.org/z/Yhh7KvrP9
    Wird aber meiner Meinung nach nicht unbedingt besser dadurch.


Anmelden zum Antworten