Forum: PC-Programmierung C++17, Sind Multi Parameter Packs möglich?


von cppbert3 (Gast)


Lesenswert?

zur Reduzierung von Boilerplate Code um mich an eine "C-Artige" 
Schnittstelle zu adaptieren die schon existiert implementiere
für jede Klasse die ich anbinden möchte ein Template was das 
durchreichen von Parametern stark vereinfacht und dabei noch 
Kontext-Keys (für ein Storage-System) mitliefert - jetzt wollte ich den 
immer (bis auf die Keys) gleiche Code aus den InnerEvalAndStore mit 
einem allgemeinen OuterEvalAndStore Template ersetzen scheitere aber 
aber daran die Varianten Anzahl an Keys und die Variante Anzahl an 
Methode-Paramater miteinander zu kombinieren

hat jemand eine Idee wie man so was machen könnte?

hier mein etwas längerer Beispiel-Code - Sorry :{

gcc.godbolt.org: https://gcc.godbolt.org/z/9ceG1z
1
#include <string>
2
3
// "C-Artige"-Schnittstelle an die ich mich adaptieren will
4
5
void eval_func( double target_, double value_ ){}
6
void eval_func( int target_, int value_ ){}
7
void eval_func( const std::string& target_, const char* value_ ){}
8
9
void store_func_A( int key1_, const std::string& key2_, double value_ ){}
10
void store_func_B( int key1_, const std::string& key2_, const std::string& value_ ){}
11
void store_func_C( double key1_, int value_ ){}
12
13
/*
14
// wie kann man multi parameter packs realisieren?
15
16
template <auto StoreFunc, typename ValueType, typename... Values, typename... Keys>
17
inline void OuterEvalAndStore(ValueType& target_, Values&&... values_, Keys&&... keys_)
18
{
19
    // hier passiert noch viel mehr im Original
20
    eval_func(target_, std::forward<Values>(values_)...);
21
    StoreFunc(std::forward<Keys>(keys_)..., target_);
22
}
23
*/
24
25
class Test1
26
{
27
private:
28
    template <auto StoreFunc, typename ValueType, typename... Values>
29
    inline void InnerEvalAndStore( ValueType& target_, Values&&... values_ )
30
    {
31
        // hier passiert noch viel mehr im Original
32
        eval_func( target_, std::forward<Values>( values_ )... );
33
        StoreFunc( m_key1, m_key2, target_ ); // key1, key2
34
35
        //OuterEvalAndStore(target_, {values_}, {m_key1, m_key2});
36
    }
37
38
public:
39
    void setA( double A_ ){ InnerEvalAndStore<store_func_A>( m_A, A_ ); }
40
    void setB( const char* B_ ){ InnerEvalAndStore<store_func_B>( m_B, B_ ); }
41
    //...
42
43
private:
44
    //keys (ueber ctor gesetzt)
45
    int m_key1{};
46
    std::string m_key2;
47
48
    //targets
49
    double m_A{};
50
    std::string m_B;
51
    //...
52
};
53
54
class Test2
55
{
56
private:
57
    template <auto StoreFunc, typename ValueType, typename... Values>
58
    inline void InnerEvalAndStore( ValueType& target_, Values&&... values_ )
59
    {
60
        // hier passiert noch viel mehr im Original
61
        eval_func( target_, std::forward<Values>( values_ )... );
62
        StoreFunc( m_key1, target_ ); // key1
63
64
        //OuterEvalAndStore(target_, {values_}, {m_key1});
65
    }
66
67
public:
68
    void setC( int C_ ){ InnerEvalAndStore<store_func_C>( m_C, C_ ); }
69
    //...
70
71
private:
72
    //keys (ueber ctor gesetzt)
73
    double m_key1{};
74
75
    //targets
76
    int m_C{};
77
    //...
78
};
79
80
int main()
81
{
82
    Test1 t1;
83
    t1.setA( 23.0 );
84
    t1.setB( "hello" );
85
86
    Test2 t2;
87
    t2.setC( 234 );
88
}

bei Stackoverflow habe ich so ein Beispiel gefunden was denke ich in die 
richtige Richtung geht aber ich keine Ahnung wie ich meine Key-Inhalte 
zwischenspeichern kann

https://stackoverflow.com/questions/9831501/how-can-i-have-multiple-parameter-packs-in-a-variadic-template
1
template<typename... First>
2
struct Obj
3
{
4
    template<typename... Second>
5
    static void Func()
6
    {
7
        std::cout << sizeof...(First) << std::endl;
8
        std::cout << sizeof...(Second) << std::endl;
9
    }
10
};

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Man kann sich mit Tupeln behelfen. Der Umweg über das Lambda vermeidet 
die direkte Übergabe des ValueType-Parameters.
1
#include <string>
2
#include <tuple>
3
4
void eval_func( double target_, double value_ ){}
5
void eval_func( int target_, int value_ ){}
6
void eval_func( const std::string& target_, const char* value_ ){}
7
8
void store_func_A( int key1_, const std::string& key2_, double value_ ){}
9
void store_func_B( int key1_, const std::string& key2_, const std::string& value_ ){}
10
void store_func_C( double key1_, int value_ ){}
11
12
template <auto StoreFunc, typename ValueType, typename... Values, typename... Keys>
13
inline void OuterEvalAndStore(ValueType target_, std::tuple<Values...> values_, std::tuple<Keys...> keys_)
14
{
15
    // Diese Variante würde die explizite Übergabe von ValueType erfordern, wie unten auskommentiert gezeigt
16
//    std::apply (static_cast<void (*) (ValueType, Values...)> (eval_func)*/, std::tuple_cat (std::tuple<ValueType&> { target_ }, std::move (values_)));
17
    
18
    // Umweg über Lambda wählt automatisch den richtigen Overload
19
    std::apply ([&] (auto&&... values) { eval_func (target_, std::forward<decltype(values)> (values)...); }, std::move (values_));
20
    
21
    std::apply (StoreFunc, std::tuple_cat (std::move (keys_), std::tuple<ValueType&> { target_ }));
22
}
23
24
int main () {
25
  double t1;
26
  OuterEvalAndStore <store_func_A/* , double */> (t1, std::tuple { 3.14 }, std::tuple { 42, std::string { "foo" } });
27
  
28
  std::string t2;
29
  OuterEvalAndStore <store_func_B/*, const std::string& */> (t2, std::tuple { "42" }, std::tuple { 42 , std::string { "foo" } });
30
  
31
  int t3;
32
  OuterEvalAndStore <store_func_C/*, int*/> (t3, std::tuple { 42 }, std::tuple { 3.14 });
33
}

von cppbert3 (Gast)


Lesenswert?

Direkt ins Schwarze beim 1. Versuch - danke Niklas

gcc.godbolt.org: https://gcc.godbolt.org/z/f37jr8

und so lange meine Keys keine Strings sind ist das inlining auch super
1
#include <tuple>
2
#include <string>
3
4
// "C-Artige"-Schnittstelle an die ich mich adaptieren will
5
6
void eval_func(double target_, double value_)
7
{
8
    int brk = 1;
9
}
10
void eval_func(int target_, int value_){}
11
void eval_func(const std::string& target_, const char* value_){}
12
void store_func_A(int key1_, const std::string& key2_, double value_){}
13
void store_func_B(int key1_, const std::string& key2_, const std::string& value_){}
14
void store_func_C(double key1_, int value_){}
15
16
template <auto StoreFunc, typename ValueType, typename... Values, typename... Keys>
17
inline void OuterEvalAndStore(ValueType target_, std::tuple<Values...> values_, std::tuple<Keys...> keys_)
18
{
19
    // Diese Variante würde die explizite Übergabe von ValueType erfordern, wie unten auskommentiert gezeigt
20
    //    std::apply (static_cast<void (*) (ValueType, Values...)> (eval_func)*/, std::tuple_cat (std::tuple<ValueType&> { target_ }, std::move (values_)));
21
22
    // Umweg über Lambda wählt automatisch den richtigen Overload
23
    std::apply([&](auto&&... values) { eval_func(target_, std::forward<decltype(values)>(values)...); },
24
        std::move(values_));
25
26
    std::apply(StoreFunc, std::tuple_cat(std::move(keys_), std::tuple<ValueType&>{ target_ }));
27
}
28
29
class Test1 {
30
private:
31
    template <auto StoreFunc, typename ValueType, typename... Values>
32
    inline void InnerEvalAndStore(ValueType& target_, Values&&... values_)
33
    {
34
        OuterEvalAndStore<StoreFunc>(target_, std::tuple{ std::forward<Values>(values_)... },
35
            std::tuple{ m_key1, m_key2 });
36
    }
37
38
public:
39
    void setA(double A_){ InnerEvalAndStore<store_func_A>(m_A, A_); }
40
    void setB(const char* B_){ InnerEvalAndStore<store_func_B>(m_B, B_); }
41
    //...
42
43
private:
44
    //keys (ueber ctor gesetzt)
45
    int m_key1{};
46
    std::string m_key2;
47
48
    //targets
49
    double m_A{};
50
    std::string m_B;
51
    //...
52
};
53
54
class Test2 {
55
private:
56
    template <auto StoreFunc, typename ValueType, typename... Values>
57
    inline void InnerEvalAndStore(ValueType& target_, Values&&... values_)
58
    {
59
        OuterEvalAndStore<StoreFunc>(target_, std::tuple{ std::forward<Values>(values_)... },
60
            std::tuple{ m_key1 });
61
    }
62
63
public:
64
    void setC(int C_){ InnerEvalAndStore<store_func_C>(m_C, C_); }
65
    //...
66
67
private:
68
    //keys (ueber ctor gesetzt)
69
    double m_key1{};
70
71
    //targets
72
    int m_C{};
73
    //...
74
};
75
76
int main()
77
{
78
    Test1 t1;
79
    t1.setA(23.0);
80
    t1.setB("hello");
81
82
    Test2 t2;
83
    t2.setC(234);
84
85
    return 0;
86
}

von cppbert3 (Gast)


Lesenswert?

@Niklas

bald musst du dich entscheiden ob es ein Obstkorb mit Früchten der 
Südsee oder so ein Metzger-Körbchen mit grober Leberwurst und Speck 
werden soll :)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

cppbert3 schrieb:
> OuterEvalAndStore<StoreFunc>(target_, std::tuple{
> std::forward<Values>(values_)... },
>             std::tuple{ m_key1, m_key2 });

Versuch mal Referenzen in das Tupel zu speichern, dann vermeidet man das 
Kopieren der Keys, dürfte inlined auch kompakter werden:
1
OuterEvalAndStore<StoreFunc>(target_, std::tuple{ std::forward<Values>(values_)... }, std::tie (m_key1, m_key2));

cppbert3 schrieb:
> bald musst du dich entscheiden ob es ein Obstkorb mit Früchten der
> Südsee oder so ein Metzger-Körbchen mit grober Leberwurst und Speck
> werden soll :)

Haha, na andere sind hier ja auch sehr produktiv ;-)

von cppbert3 (Gast)


Lesenswert?

Niklas G. schrieb:
> cppbert3 schrieb:
>> OuterEvalAndStore<StoreFunc>(target_, std::tuple{
>> std::forward<Values>(values_)... },
>>             std::tuple{ m_key1, m_key2 });
>
> Versuch mal Referenzen in das Tupel zu speichern, dann vermeidet man das
> Kopieren der Keys, dürfte inlined auch kompakter werden:
> OuterEvalAndStore<StoreFunc>(target_, std::tuple{
> std::forward<Values>(values_)... }, std::tie (m_key1, m_key2));

Ergibt Micro-Unterschiede - aber ich schreibe es mir mal dazu

> cppbert3 schrieb:
>> bald musst du dich entscheiden ob es ein Obstkorb mit Früchten der
>> Südsee oder so ein Metzger-Körbchen mit grober Leberwurst und Speck
>> werden soll :)
>
> Haha, na andere sind hier ja auch sehr produktiv ;-)

auf jeden Fall, jetzt gerade bist aber nur du hier - und hast mir auch 
schon echt oft schnell und gut geholfen, oder stalkst du meine Posts und 
ich muss mir sorgen machen? :)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

cppbert3 schrieb:
> Ergibt Micro-Unterschiede - aber ich schreibe es mir mal dazu

Gut, könnte sein dass der Compiler sonst nicht sieht dass das Kopieren 
des Strings unnötig ist und ihn eben unnötig kopiert.

cppbert3 schrieb:
> und hast mir auch
> schon echt oft schnell und gut geholfen, oder stalkst du meine Posts und
> ich muss mir sorgen machen? :)

Ne, ich klicke nur auf alles wo "C++" steht ;-) Die C++ Community auf 
Stack-Overflow ist auch sehr gut, und für schnelle Hilfe ist der 
Cpplang-Slack auch gut: https://cppalliance.org/slack/

PS: Mir fällt gerade auf dass ich einen Copy-Paste-Fehler gemacht habe, 
in der Parameter-Liste sollte "target_" eine Referenz sein:
1
template <auto StoreFunc, typename ValueType, typename... Values, typename... Keys>
2
inline void OuterEvalAndStore(const ValueType& target_, std::tuple<Values...> values_, std::tuple<Keys...> keys_)
3
{
4
    std::apply([&](auto&&... values) { eval_func(target_, std::forward<decltype(values)>(values)...); },
5
        std::move(values_));
6
7
    std::apply(StoreFunc, std::tuple_cat(std::move(keys_), std::tuple<const ValueType&>{ target_ }));
8
}

: Bearbeitet durch User
von cppbert3 (Gast)


Lesenswert?

Niklas G. schrieb:
> cppbert3 schrieb:
>> Ergibt Micro-Unterschiede - aber ich schreibe es mir mal dazu
>
> Gut, könnte sein dass der Compiler sonst nicht sieht dass das Kopieren
> des Strings unnötig ist und ihn eben unnötig kopiert.

dann mache ich es jetzt rein

Niklas G. schrieb:
> PS: Mir fällt gerade auf dass ich einen Copy-Paste-Fehler gemacht habe,
> in der Parameter-Liste sollte "target_" eine Referenz sein:

Mensch, Niklas!!!

von cppbert3 (Gast)


Lesenswert?

in meiner echten Implementierung habe ich jetzt mehrere (3) 
Funktion-Templates welche die ...Keys, ...Values und noch ein paar 
andere Objekte brauchen

die ...Keys und anderen Objekte brauchen alle

könnte man die irgendwie in eine Template-Klasse stecken welche ich dann 
lokal privat Instanziere damit ich die Keys immer da habe

also in der Art
1
template<typename... Keys>
2
class Helper
3
{
4
  Helper(obj1, obj2, std::tuple<Keys...> keys_):m_keys(keys_){}
5
6
  template <auto StoreFunc, typename ValueType, typename... Values>
7
  inline void EvalAndStore(const ValueType& target_, std::tuple<Values...> values_)
8
{
9
    std::apply([&](auto&&... values) { eval_func(target_, std::forward<decltype(values)>(values)...); },
10
        std::move(values_));
11
12
    std::apply(StoreFunc, std::tuple_cat(std::move(m_keys), std::tuple<const ValueType&>{ target_ }));
13
}
14
15
private:
16
  std::tuple<Keys...> m_keys;
17
};
18
19
class Test
20
{
21
public:
22
  Test():
23
    m_helper(objX, objY, m_key1, m_key2)
24
  {
25
  }
26
private:// internals
27
  Helper m_helper;
28
29
public:
30
  
31
};

und wenn das geht wie kann man verhindern das diese lokale Hilfsobjekt 
die Keys usw. kopiert

Eine Idee?

von cppbert3 (Gast)


Lesenswert?

in public fehlte noch
1
public:
2
    void setC(int C_){ m_helper.EvalAndStore<store_func_C>(m_C, C_); }
3
    //...

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Sieht doch schon gut aus. Durch den strategischen Einsatz von std::move 
im Konstruktur, und Overloads für verschiedene Referenz-Typen des 
"keys_"-Arguments kann man Kopien vermeiden, ohne die Typsicherheit zu 
verlieren:
1
class Helper {
2
  Helper (obj1, obj2, std::tuple<Keys...>&& keys_) : m_keys (std::move (keys_)) {}
3
  Helper (obj1, obj2, const std::tuple<Keys...>& keys_) : m_keys (keys_) {}

Oder nur den 1. gezeigten Overload behalten, falls man sowieso nie 
"const&" braucht.

von cppbert3 (Gast)


Lesenswert?

Niklas G. schrieb:
> Sieht doch schon gut aus. Durch den strategischen Einsatz von
> std::move
> im Konstruktur, und Overloads für verschiedene Referenz-Typen des
> "keys_"-Arguments kann man Kopien vermeiden, ohne die Typsicherheit zu
> verlieren:
> class Helper {
>   Helper (obj1, obj2, std::tuple<Keys...>&& keys_) : m_keys (std::move
> (keys_)) {}
>   Helper (obj1, obj2, const std::tuple<Keys...>& keys_) : m_keys (keys_)
> {}
> Oder nur den 1. gezeigten Overload behalten, falls man sowieso nie
> "const&" braucht.

meine Keys sind unveränderlich also eigentlich immer (const) members - 
die duerfen sich nie aendern

von cppbert3 (Gast)


Lesenswert?

das ist mein Ergebnis
1
template <typename Store, typename... Keys>
2
class ModifierHelper
3
{
4
public:
5
    ModifierHelper( Store& history_, const std::tuple<Keys...>& keys_ )
6
        : m_store( store_ ),
7
          m_keys( keys_ )
8
    {
9
    }
10
11
    template <auto StoreFunc, typename ValueType>
12
    inline void EvalAndStore( ValueType value_, ValueType& target_ )
13
    {
14
        if( target_ == value_ )
15
        {
16
            return;
17
        }
18
        target_ = value_;
19
        std::apply( StoreFunc, std::tuple_cat( std::make_tuple( m_store ), std::move( m_keys ),
20
                                                std::tuple<ValueType&>{ value_ } ) );
21
    }
22
23
private:
24
    Store& m_store;
25
    std::tuple<Keys...> m_keys;
26
};

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

So wird aber immer kopiert... Wär's nicht sinnvoller, aus "m_keys" eine 
Referenz zu machen? Sind die Keys ein Member der äußeren Klasse (oben: 
"Test")? Dann hast du eine gewisse Duplikation.

von cppbert3 (Gast)


Lesenswert?

Niklas G. schrieb:
> So wird aber immer kopiert... Wär's nicht sinnvoller, aus "m_keys"
> eine
> Referenz zu machen? Sind die Keys ein Member der äußeren Klasse (oben:
> "Test")? Dann hast du eine gewisse Duplikation.

ja das sind die Keys der äußeren Klasse von der es eben X Varianten gibt
die Keys sind eigentlich sogar const (mache ich jetzt) weil die niemals 
veraendert werden duerfen

wie wäre es denn ideal wenn die m_key?s immer const wären und problemlos 
per const& uebergeben werden können

bisher erzeuge ich so mein HelperObject

private: // internal
    ModifierHelper<Store, decltype( m_key1 ), decltype( m_key2 )> 
m_helper;

und im ctor dann so
   , m_helper( m_store, { m_key1, m_key2 } )

Beitrag #6507244 wurde von einem Moderator gelöscht.
von cppbert3 (Gast)


Lesenswert?

habs mals const&-ed
1
private:
2
    //keys (ueber ctor gesetzt)
3
    const double m_key1{};
4
    const int m_key2{};
5
private: // internal
6
    const ModifierHelper<Store, decltype( m_key1 )&, decltype( m_key2 )&> 
7
m_helper;

und im ctor dann so
1
   , m_helper( m_store, { m_key1, m_key2 } )

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Das heißt aber dass ModifierHelper nur Referenzen in das enthaltende 
Objekt enthält. Daher ist es eher sinnlos permanent eine Instanz davon 
zu haben. Wäre es nicht sinnvoller eine inline-Funktion zu definieren, 
welche ein temporäres ModifierHelper-Objekt erzeugt und zurückgibt, mit 
dem man dann jeweils arbeitet? Durch das inlining wird das Erstellen des 
Objekts wegoptimiert.

von cppbert3 (Gast)


Lesenswert?

Niklas G. schrieb:
> Das heißt aber dass ModifierHelper nur Referenzen in das
> enthaltende
> Objekt enthält. Daher ist es eher sinnlos permanent eine Instanz davon
> zu haben. Wäre es nicht sinnvoller eine inline-Funktion zu definieren,
> welche ein temporäres ModifierHelper-Objekt erzeugt und zurückgibt, mit
> dem man dann jeweils arbeitet? Durch das inlining wird das Erstellen des
> Objekts wegoptimiert.

das hört sich auch gut an

d.h. ich ersetze meinen m_helper+ctor init

durch so eine Member-Funktion?
1
auto& helper() const
2
{
3
  return ModifierHelper<Store>(m_store,  m_key1, m_key2);
4
}

und dann rufe ich die in allen meinen settern/gettern auf
1
public:
2
    void setC(int C_){ helper().EvalAndStore<store_func_C>(m_C, C_); }

oder?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Ja ganz genau. Am Besten die "helper()" -Funktion noch mit "inline" 
markieren.

von cppbert3 (Gast)


Lesenswert?

muss als Kopie zurueckliefern sonst ists ein wenig kaputt

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.