Forum: PC-Programmierung Kann mein C++ Template selbständig den Scope deducten


von CppBert (Gast)


Lesenswert?

Ich habe hier eine Software die ich ein bisschen "verbessern" darf - 
alles  ist dynamic_cast basiert (nicht von mir) und ich will 
häppchenweise die Software vereinfachen um später das ganze komplett zu 
ersetzen - jetzt gerade geht es mir aber nur darum mal ohne große 
Umbauten den Code aufräumen zu können - um weniger Fehler zu machen

also habe ich mir ein Helper Template geschrieben das prüft ob
ein ein Base-Class Pointer auf einen gegeben Typ gecastet werden kann 
und falls ja eine entsprechende Empfänger-Methode aufruft - klappt auch 
alles so wie es soll.

Es gibt nur eine Unschönheit das ich in dem eigentlich generischem 
Helper
relativ früh den Target-Scope-Type in diesem Beispiel Test_t angeben 
muss obwohl ich denke das ich das irgendwie auch automatisch aus dem 
this Pointer den ich eh mitgeben dedukten könnte, komme aber nicht drauf 
wie ich das lösen kann - ich will aber vermeiden einen weiteren 
Template-Parameter zu haben - falls das möglich ist

Hat jemand eine Idee

1
#include <functional>
2
#include <iostream>
3
4
struct BaseType_t 
5
{
6
  virtual ~ BaseType_t () = default;
7
};
8
9
 
10
// es gibt viele BaseType varianten...
11
struct TypeA_t:BaseType_t 
12
{
13
  int value = 123;
14
};
15
16
 
17
struct TypeB_t:BaseType_t 
18
{
19
  double value = 123.321;
20
};
21
22
 
23
struct Test_t;
24
 
25
//!!! Das Template will ich verbessern !!!
26
template < typename Type, typename Scope > 
27
bool TypeBasedDispatch (const BaseType_t * base_type, Scope scope, 
28
  std::function <void (Test_t* /*!!! Frage: wie kann ich hier generisch bleiben !!!*/, const Type &) > function)
29
{
30
  // kann ich den Test_t* z.B. durch declspec(scope) oder sowas ersetzen um nicht mehr Parameter
31
  // beim Aufruf zu bekommen? es sollte mit C++11 funktionieren
32
  const Type *typed = dynamic_cast < const Type * >(base_type);
33
  if (typed) {
34
    function (scope, *typed);
35
    return true;
36
  }
37
  return false;
38
}
39
40
struct Test_t 
41
{
42
  void OnTypeA (const TypeA_t & type) 
43
  {
44
    std::cout << "OnTypeA! value: " << type.value << std::endl;
45
  } 
46
47
  void OnTypeB (const TypeB_t & type) 
48
  {
49
    std::cout << "OnTypeB! value: " << type.value << std::endl;
50
  } 
51
 
52
  void OnType (BaseType_t * base_type) 
53
  {
54
    if (TypeBasedDispatch < TypeA_t > (base_type, this, &Test_t::OnTypeA)){
55
    return;
56
    }
57
    
58
    if (TypeBasedDispatch < TypeB_t > (base_type, this, &Test_t::OnTypeB)){
59
    return;
60
    }
61
  }
62
};
63
64
int main () 
65
{
66
  Test_t t;
67
  
68
  TypeA_t a;
69
  TypeB_t b;
70
  
71
  t.OnType(&a);
72
  t.OnType(&b);
73
}

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Dein eigentliches Problem scheint ja zu sein, Member-Funktionen per 
std::function aufzurufen ohne den this-Pointer explizit mitgeben zu 
müssen. Das geht z.B. per std::bind:
1
template < typename Type > 
2
bool TypeBasedDispatch (const BaseType_t * base_type, std::function <void (const Type &) > function)
3
{
4
  const Type *typed = dynamic_cast < const Type * >(base_type);
5
  if (typed) {
6
    function (*typed);
7
    return true;
8
  }
9
  return false;
10
}
11
12
struct Test_t 
13
{
14
  void OnTypeA (const TypeA_t & type) 
15
  {
16
    std::cout << "OnTypeA! value: " << type.value << std::endl;
17
  } 
18
19
  void OnTypeB (const TypeB_t & type) 
20
  {
21
    std::cout << "OnTypeB! value: " << type.value << std::endl;
22
  } 
23
 
24
  void OnType (BaseType_t * base_type) 
25
  {
26
    if (TypeBasedDispatch < TypeA_t > (base_type, std::bind(&Test_t::OnTypeA, this, std::placeholders::_1))){
27
    return;
28
    }
29
    
30
    if (TypeBasedDispatch < TypeB_t > (base_type, std::bind(&Test_t::OnTypeB, this, std::placeholders::_1))){
31
    return;
32
    }
33
  }
34
};

Alternativ kannst du auch direkt mit Member-Funktion-Pointern arbeiten, 
das hat aber den Nachteil dass TypeBasedDispatch dann nur noch mit 
Member-Funktionen funktioniert:
1
template < typename Ret, typename Class, typename Type > 
2
bool TypeBasedDispatch (const BaseType_t * base_type, Ret (Class::*memFun) (const Type&), Class* thisPtr)
3
{
4
  const Type *typed = dynamic_cast < const Type * >(base_type);
5
  if (typed) {
6
    (thisPtr->*memFun) (*typed);
7
    return true;
8
  }
9
  return false;
10
}
11
12
struct Test_t 
13
{
14
  void OnTypeA (const TypeA_t & type) 
15
  {
16
    std::cout << "OnTypeA! value: " << type.value << std::endl;
17
  } 
18
19
  void OnTypeB (const TypeB_t & type) 
20
  {
21
    std::cout << "OnTypeB! value: " << type.value << std::endl;
22
  } 
23
 
24
  void OnType (BaseType_t * base_type) 
25
  {
26
    if (TypeBasedDispatch (base_type, &Test_t::OnTypeA, this)){
27
    return;
28
    }
29
    
30
    if (TypeBasedDispatch (base_type, &Test_t::OnTypeB, this)){
31
    return;
32
    }
33
  }
34
};
Ein netter Nebeneffekt ist dass dank Inferierung der template-Parameter 
das TypeA_t gar nicht mehr übergeben werden muss.

Schau dir auch mal std::variant und std::visit an, das ist so ähnlich...

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Vielleicht so?
1
struct Test_t {
2
  void OnTypeA (const TypeA_t & type) {
3
    std::cout << "OnTypeA! value: " << type.value << std::endl;
4
  } 
5
6
  void OnTypeB (const TypeB_t & type) {
7
    std::cout << "OnTypeB! value: " << type.value << std::endl;
8
  } 
9
  template<typename BT>
10
  void OnType (const BT* base_type) {
11
      if constexpr(std::is_same_v<BT, TypeA_t>) {
12
          OnTypeA(*base_type);
13
      }       
14
      if constexpr(std::is_same_v<BT, TypeB_t>) {
15
          OnTypeB(*base_type);
16
      }       
17
  }
18
};

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Vielleicht so?

Ich habe es so verstanden, dass OnType immer mit einem BaseType_t* 
aufgerufen wird, und man eine Laufzeit-Unterscheidung braucht - daher 
das dynamic_cast. Sonst könnte man ja einfach mehrere Overloads benutzen 
- OnType (const TypeA_t*), OnType(const TypeB_t*), ... - ist auch etwas 
übersichtlicher als per if-constexpr und is_same ...

von Wilhelm M. (wimalopaan)


Lesenswert?

Niklas G. schrieb:
> Wilhelm M. schrieb:
>> Vielleicht so?
>
> Ich habe es so verstanden, dass OnType immer mit einem BaseType_t*
> aufgerufen wird, und man eine Laufzeit-Unterscheidung braucht - daher
> das dynamic_cast. Sonst könnte man ja einfach mehrere Overloads benutzen
> - OnType (const TypeA_t*), OnType(const TypeB_t*), ... - ist auch etwas
> übersichtlicher als per if-constexpr und is_same ...

Ich habe gar nicht verstanden, was er will ...

Natürlich kann man ein overload-set benutzen, oder auch double-dispatch. 
Allerdings dachte ich mir, das er mindestens ggf. ein type-mapping per 
meta-function dort einbauen will als customizatoin-point.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Ich glaube er will einfach nur per dynamic_cast den Laufzeit-Typ eines 
Objekts unterscheiden und dann dementsprechend unterschiedliche 
Funktionen aufrufen. Eigentlich nur Single-Dispatch, aber nicht über 
virtuelle Funktionen des Objekts selbst. Er will das dynamic_cast aber 
kapseln (in OnType) und das Problem besteht lediglich darin, die 
verschiedenen Funktionen aufzurufen. Das eigentliche Problem hat also 
nichts mit Typ-Unterscheidung oder Dispatch zu tun, sondern bloß damit 
wie man Member-Funktionen indirekt (std::function oder Member Function 
Pointer) aufruft.

von CppBert1 (Gast)


Lesenswert?

>Ich glaube er will einfach nur per dynamic_cast den Laufzeit-Typ eines
>Objekts unterscheiden

die jetzige Software macht es so - ich will nicht unbedingt

>Das eigentliche Problem hat also
>nichts mit Typ-Unterscheidung oder Dispatch zu tun, sondern bloß damit
>wie man Member-Funktionen indirekt (std::function oder Member Function
>Pointer) aufruft.

Ja genau - die Software an der ich arbeite ist riesig (>1Mio Zeile Code) 
und ich möchte in vielen kleinen (Testsicheren) Stufen das jetziges 
Konzept (dynamic_cast) immer mehr in eine Richtung bringen das ich es 
schlussendlich einfacher ersetzen kann ohne zu viel kaputt zu machen - 
diese Helper-Templates sind nur Refactoring-Begünstiger die eine Weile 
lang im Code verbleiben dürfen

von CppBert1 (Gast)


Lesenswert?

1
template < typename Ret, typename Class, typename Type > 
2
bool TypeBasedDispatch (const BaseType_t * base_type, Ret (Class::*memFun) (const Type&), Class* thisPtr)
3
{
4
  const Type *typed = dynamic_cast < const Type * >(base_type);
5
  if (typed) {
6
    (thisPtr->*memFun) (*typed);
7
    return true;
8
  }
9
  return false;
10
}
11
12
struct Test_t 
13
{
14
  void OnTypeA (const TypeA_t & type) 
15
  {
16
    std::cout << "OnTypeA! value: " << type.value << std::endl;
17
  } 
18
19
  void OnTypeB (const TypeB_t & type) 
20
  {
21
    std::cout << "OnTypeB! value: " << type.value << std::endl;
22
  } 
23
 
24
  void OnType (BaseType_t * base_type) 
25
  {
26
    if (TypeBasedDispatch (base_type, &Test_t::OnTypeA, this)){
27
    return;
28
    }
29
    
30
    if (TypeBasedDispatch (base_type, &Test_t::OnTypeB, this)){
31
    return;
32
    }
33
  }
34
};

Ich habe mich für deinen MemberFunction-Variante entschieden - passt 
super und funktioniert - Danke nochmal

jetzt würde ich gerne diesen Teil
1
  void OnType (BaseType_t * base_type) 
2
  {
3
    if (TypeBasedDispatch (base_type, &Test_t::OnTypeA, this)){
4
    return;
5
    }
6
    
7
    if (TypeBasedDispatch (base_type, &Test_t::OnTypeB, this)){
8
    return;
9
    }
10
11
    assert(false);
12
  }

in sowas umwandeln - könnte das mit Variadic Templates gehen?

Dispatcher<
  // die runtime Anteile wie der base_type/this
  // muessen dann von aussen kommen
  TypeBasedDispatch(&Test_t::OnTypeA),
  TypeBasedDispatch(&Test_t::OnTypeB)
> dispatcher;

void OnType (BaseType_t * base_type)
{
  dispatcher.dispatch(base_type, this);
}

es muss denk eich per Variadic Template sein weil die TypeBasedDispatch 
einzeln spezalisiert sind - eine Idee wie man das erreichen könnte?

Info: der base_type Pointer kann nicht zur Kompilezeit ausgewertet 
werden - kommt ganz wo anders her (und ich kann das Prinzip auch nicht 
total verändern - hängt viel zu viel Code drann u.a. nicht von mir ist)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

CppBert1 schrieb:
> in sowas umwandeln - könnte das mit Variadic Templates gehen?

So?
1
struct Test_t 
2
{
3
  void OnType (const TypeA_t & type) 
4
  {
5
    std::cout << "OnTypeA! value: " << type.value << std::endl;
6
  } 
7
8
  void OnType (const TypeB_t & type) 
9
  {
10
    std::cout << "OnTypeB! value: " << type.value << std::endl;
11
  } 
12
  
13
  // In C++17 geht es etwas kürzer, wird möglicherweise etwas effizienter kompiliert
14
/*  template <typename... Types>
15
  void TypeBasedDispatch (const BaseType_t* p) {
16
      ((dynamic_cast<const Types*> (p) != nullptr && (OnType (*dynamic_cast<const Types*> (p)), true)) || ... );
17
  } */
18
  
19
  // In C++11 muss es so
20
  void nop (...) {}
21
  
22
  template <typename... Types>
23
  void TypeBasedDispatch (const BaseType_t* p) {
24
      nop ((dynamic_cast<const Types*> (p) != nullptr && (OnType (*dynamic_cast<const Types*> (p)), true)) ... );
25
  }
26
  
27
  
28
  void OnType (BaseType_t * base_type)
29
  {
30
     TypeBasedDispatch<TypeA_t, TypeB_t> (base_type);
31
  }
32
};

Man hat einfach mehrere Überladungen von OnType für die verschiedenen 
Typen. TypeBasedDispatch ruft die passende Überladung auf, je nachdem 
was es ist. Die letzte Überladung ist für BaseType_t * und ruft 
TypeBasedDispatch auf. Hat den kleinen netten Vorteil, dass man von 
außen einfach nur OnType(obj) aufrufen muss, und es wird immer direkt 
das richtige aufgerufen - wenn obj schon zur Kompile-Zeit ein TypeA_t 
ist, wird direkt der richtige Overload aufgerufen, ohne den Overhead per 
dynamic_cast.

Ein weitere Vorteil an den Overloads ist, dass man auch templates nehmen 
kann:
1
template <typename T>
2
struct TypeX : BaseType_t {
3
  T value;
4
  TypeX (const T& src) : value (src) {}
5
};
6
7
  template <typename T>
8
  void OnType (const TypeX<T> & type) 
9
  {
10
    std::cout << "OnTypeX<T>! value: " << type.value << std::endl;
11
  } 
12
13
  void OnType (BaseType_t * base_type)
14
  {
15
     TypeBasedDispatch<TypeA_t, TypeB_t, TypeX<int>, TypeX<std::string>> (base_type);
16
  }
17
18
  TypeX<std::string> c { "foobar" };
19
  TypeX<int> d { 42 };
20
  
21
  t.OnType(&c);
22
  t.OnType(&d);

: Bearbeitet durch User
von CppBert1 (Gast)


Lesenswert?

Perfekt läuft sofort

Wie kann man jetzt noch direkt beim 1. Treffer returnen (also nicht 
weiter die OnTypes aufrufen) und wenn gar kein cast funktioniert hat ein 
assert(false) verursachen?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Das ist beides in C++11 etwas lästig. Die gezeigte C++17-Version erfüllt 
den ersten Wunsch bereits (Short-Circuit Evaluation in der 
fold-expression), der zweite lässt sich leicht ergänzen:
1
  void TypeBasedDispatch (const BaseType_t* p) {
2
    [[maybe_unused]] bool res = ((dynamic_cast<const Types*> (p) != nullptr && (OnType (*dynamic_cast<const Types*> (p)), true)) || ... );
3
    assert (res);
4
  }

Kurioserweise löst dieser Code bei -Wall eine Warnung im GCC 7 aus, die 
aber bei aktuellen Versionen behoben ist.

von CppBert1 (Gast)


Lesenswert?

Frage 1: Ich bräuchte beides für C++11 - was von deinem Beispiel kann 
ich dann so einsetzen?
Frage 2: gibt es einen Möglichkeit die OnType-Routine auch per 
Template-Parameter aus zu tauschen

so in der Art:
1
template <typename ToCallFunction, typename... Types>
2
  void TypeBasedDispatch (const BaseType_t* p) {
3
      nop ((dynamic_cast<const Types*> (p) != nullptr && (ToCallFunction(*dynamic_cast<const Types*> (p)), true)) ... );
4
  }

vielen Dank für deine bisherige Hilfestellungen

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

CppBert1 schrieb:
> Frage 1: Ich bräuchte beides für C++11 - was von deinem Beispiel kann
> ich dann so einsetzen?

Welcher aktuelle Compiler kann denn nur C++11, aber nichtmal C++14? Wenn 
es schon vorgedrungen ist dass die C++-Entwicklung wieder weiter geht, 
kann man auch am Ball bleiben und nicht C++11 das neue C++98 werden 
lassen... Aber okay, so geht es in C++11:
1
template <typename Base, typename Obj, typename... Types>
2
struct TypeBasedDispatch;
3
4
template <typename Base, typename Obj, typename T1, typename... Rest>
5
struct TypeBasedDispatch<Base, Obj, T1, Rest...> {
6
  static void call (Obj& obj, Base* base) {
7
    T1* res = dynamic_cast<T1*> (base);
8
    if (res)
9
      obj.OnType (*res);
10
    else
11
      TypeBasedDispatch<Base, Obj, Rest...>::call (obj, base);
12
  }
13
};
14
15
template <typename Base, typename Obj>
16
struct TypeBasedDispatch<Base, Obj> {
17
  static void call (Obj&, Base*) {
18
    assert (false);
19
  }
20
};
21
22
template <typename... Types, typename Obj, typename Base>
23
void typeBasedDispatch (Obj& obj, Base* base) {
24
  TypeBasedDispatch<Base, Obj, Types...>::call (obj, base);
25
}
26
27
struct Test_t 
28
{
29
  void OnType (const TypeA_t & type) 
30
  {
31
    std::cout << "OnTypeA! value: " << type.value << std::endl;
32
  } 
33
34
  void OnType (const TypeB_t & type) 
35
  {
36
    std::cout << "OnTypeB! value: " << type.value << std::endl;
37
  } 
38
  
39
  void OnType (BaseType_t * base_type)
40
  {
41
     typeBasedDispatch <TypeA_t, TypeB_t> (*this, base_type);
42
  }
43
};

Durch die Auslagerung ist man nebenbei nicht mehr auf Test_t festgelegt.

CppBert1 schrieb:
> Frage 2: gibt es einen Möglichkeit die OnType-Routine auch per
> Template-Parameter aus zu tauschen

Genau das geht bei den Overloads nicht, weil man ja alle Overloads 
übergeben müsste - nicht sehr schön. Man kann aber wie gesagt einfach 
die Test_t -Klasse austauschen und eben für jedes Verhalten eine andere 
Klasse nehmen.

von CppBert1 (Gast)


Lesenswert?

Danke wieder mal - jetzt muss ich mich erst mal da durch schlumpfen

>Welcher aktuelle Compiler kann denn nur C++11

Wer sagt denn das ich einen aktuellen Kompiler verwenden darf :)
bei mir ist es der VS2015 - und ein Wechsel verursacht gerade zu viel 
Testaufwand - angedacht ist es aber - genau wegen solchen Problemen

>Genau das geht bei den Overloads nicht, weil man ja alle Overloads
>übergeben müsste - nicht sehr schön. Man kann aber wie gesagt einfach
>die Test_t -Klasse austauschen und eben für jedes Verhalten eine andere
>Klasse nehmen.

Ich habe jetzt 2-3 Varianten von deinem Dispatcher für unterschiedliche 
Basis-Types aber werden in einer Klasse verarbeitet

also so:

BaseType1_t
  Type_1_A
  Type_1_B
  Type_1_C
die sollen an OnType1-overloads dispatcht werden

BaseType2_t
  Type_2_A
  Type_2_B
  Type_2_C
die sollen an OnType2-overloads dispatcht werden

also nicht für jeden Typ eingenes Ziel sondern für jede 
typeBasedDispatch-Instanzen ein Ziel

void OnType1 (BaseType1_t * base_type)
  {
     typeBasedDispatch <Type_1_A_t, Type_1_B_t> (*this, base_type);
     // geht an OnType1 overloads
  }

void OnType2 (BaseType2_t * base_type)
  {
     typeBasedDispatch <Type_2_A_t, Type_2_B_t> (*this, base_type);
     // geht an OnType2 overloads
  }

da werden unterschiedliche Sachen Dispatcht aber das Prinzip ist das 
gleiche

von CppBert1 (Gast)


Lesenswert?

Sorry ich bin total blind - den

template <typename Base, typename Obj, typename... Types>
struct TypeBasedDispatch;

fehlt ja nur noch ein typename Function dann sollte das gehen was ich 
will

von CppBert1 (Gast)


Lesenswert?

Nein doch nicht :)

Echt schade das es kein Methoden-Alias oder sowas gibt

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

CppBert1 schrieb:
> fehlt ja nur noch ein typename Function dann sollte das gehen was ich
> will

Typename ist für Typen... Man kann zwar (Member-)Funktionspointer 
übergeben, aber diese beziehen sich immer auf eine konkrete Funktion, 
also einen spezifischen Overload. Du kannst aber einfach alles in 1 
Klasse packen:
1
class Test_t {
2
  void OnType (Type_1_A&);
3
  void OnType (Type_1_B&);
4
  void OnType (Type_2_A&);
5
  void OnType (Type_2_B&);
6
  void OnType (BaseType1_t * base_type) {
7
    typeBasedDispatch <Type_1_A, Type_1_B> (*this, base_type);
8
  }
9
  void OnType (BaseType2_t * base_type) {
10
    typeBasedDispatch <Type_2_A, Type_2_B> (*this, base_type);
11
  }
12
};
Ansonsten einfach mehrere verschiedene Test_t machen. Ist eh 
übersichtlicher.

von CppBert (Gast)


Lesenswert?

Hab es jetzt doch geschafft verschiedene Ziel-Methoden rein zu bringen

ich habe einen weiteren Template-Parameter CallType eingefügt mit dem 
ich an der ehemaligen OnType()-Call Stelle ein schon partiell 
spezialisiertes Template OverloadCaller<CallType> mit Obj und T1 
ausspezialisiere - damit klappt es (aber jetzt habe ich das Problem das 
bei der eine Variante noch ein Parameter mehr durchgeschleift werden 
muss...)

Was ist eigentlich der Grund dafür das es in C++ Methoden-Alias oder 
sowas gibt (um solche Overloads zu erreichen) - gibt das einfach nicht 
oder geht es aus einem bestimmten Grund nicht?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

CppBert schrieb:
> gibt das einfach nicht
> oder geht es aus einem bestimmten Grund nicht?

Passt halt wenig zur Sprache. Das wäre ja ein Alias für mehrere 
Funktionen gleichen Namens; also weder ein Funktionszeiger (der zeigt 
immer auf eine bestimmte Funktion) noch ein Typ-Alias, sondern ein 
komplett neues Sprachelement. Das wird vermutlich einfach zu wenig 
nachgefragt, als dass es sinnvoll wäre, die Sprache derart umzukrempeln. 
Kennst du eine Sprache, die das kann?

In C++11 kann man alternativ ja auch einfach eine Klasse definieren, 
welche mehrere Overloads für oder einen templatisierten "operator ()" 
hat - diese Klasse entspricht dann einer "Operation" (=mehrere 
Funktionen), und dafür gibt es Typ-Aliase. Wenn ich dich richtig 
verstehe hast du ja auch genau das gemacht...

In C++14 kann man stattdessen auch template-Lambdas benutzen - das 
Lambda ist dann ein template und quasi ein alias für mehrere Funktionen:
1
template <typename Base, typename... Types>
2
struct TypeBasedDispatch;
3
4
template <typename Base, typename T1, typename... Rest>
5
struct TypeBasedDispatch<Base, T1, Rest...> {
6
  template <typename F>
7
  static void call (F&& f, Base* base) {
8
    T1* res = dynamic_cast<T1*> (base);
9
    if (res)
10
      std::forward<F> (f) (*res);
11
    else
12
      TypeBasedDispatch<Base, Rest...>::call (std::forward<F> (f), base);
13
  }
14
};
15
16
template <typename Base>
17
struct TypeBasedDispatch<Base> {
18
  template <typename F>
19
  static void call (F&&, Base*) {
20
    assert (false);
21
  }
22
};
23
24
template <typename... Types, typename F, typename Base>
25
void typeBasedDispatch (Base* base, F&& f) {
26
  TypeBasedDispatch<Base, Types...>::call (std::forward<F> (f), base);
27
}
28
29
struct Test_t 
30
{
31
  void OnType (const TypeA_t & type) 
32
  {
33
    std::cout << "OnTypeA! value: " << type.value << std::endl;
34
  } 
35
36
  void OnType (const TypeB_t & type) 
37
  {
38
    std::cout << "OnTypeB! value: " << type.value << std::endl;
39
  } 
40
  
41
  void OnType (BaseType_t * base_type)
42
  {
43
     typeBasedDispatch <TypeA_t, TypeB_t> (base_type, [&] (auto& obj) { OnType (obj); });
44
  }
45
};

Man beachte dass innerhalb von "TypeBasedDispatch" weder Test_t noch 
OnType auftauchen, und man das damit für andere Klassen und Funktionen 
wiederverwenden kann. Man könnte das auch für mehrere Lambdas ausbauen - 
man übergibt ein Lambda pro TypeX_t, und es wird jeweils das eine 
passende aufgerufen, somit gäbe es keine einzelnen OnType-Funktionen 
mehr. Man muss sich bloß was clevers überlegen, wie man gleichzeitig ein 
Parameter-Pack für die abgeleiteten Klassen und eines für die Lambdas 
übergibt...

von Wilhelm M. (wimalopaan)


Lesenswert?

Niklas G. schrieb:

> Man muss sich bloß was clevers überlegen, wie man gleichzeitig ein
> Parameter-Pack für die abgeleiteten Klassen und eines für die Lambdas
> übergibt...

Mache aus einem nackten Parameter-Pack der abgel. Klassen eine Typ-Liste 
(Typ-Container), dann ist der einzige Parameter-Pack der für die Lambdas 
...

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Mache aus einem nackten Parameter-Pack der abgel. Klassen eine Typ-Liste
> (Typ-Container)

Die obligatorische Util::TypeList<...> Klasse die jedes fortgeschrittene 
C++- Projekt braucht :-) ... Leider sieht der Aufruf dann relativ 
hässlich aus. Ohne C++17 und Fold Expressions würde die Implementation 
dank doppelter Rekursion auch unschön.

: Bearbeitet durch User
von CppBert1 (Gast)


Lesenswert?

Auf jeden Fall sehr hilfreich eure Kommentare - könnt ihr ein 
Buch/Online-Tutorial zu Variadic Templates (C++11..C++17) empfehlen?

von CppBert1 (Gast)


Lesenswert?

Das Lambda-Template Beispiel von dir wäre perfekt - und glücklicherweise 
darf ich doch C++14 verwenden - aber dein Beispiel kompiliert erst mit 
C++17 :(

wenn den den Online Kompiler auf C++14 stelle geht es nicht mehr, 
meintest du C++17?
https://onlinegdb.com/Bk536q607

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

CppBert1 schrieb:
> könnt ihr ein
> Buch/Online-Tutorial zu Variadic Templates (C++11..C++17) empfehlen?

Weiß grad keins.

CppBert1 schrieb:
> Das Lambda-Template Beispiel von dir wäre perfekt - und glücklicherweise
> darf ich doch C++14 verwenden - aber dein Beispiel kompiliert erst mit
> C++17 :(

Das von heut morgen? Kompiliert bei mit sowohl mit GCC als auch Clang 
wenn auf C++14 gestellt. Auf Godbolt's Compiler Explorer funktioniert es 
erst ab GCC 7.1, ältere Versionen haben da vermutlich einen Bug; ab 
Clang 3.5 (von 2014) geht's:

https://godbolt.org/z/sCZY8n

Anscheinend erkennt der alte GCC nicht, dass er "this" capturen muss. 
Mit einem kleinen Workaround kann man das explizit machen, dann 
funktioniert es ab GCC 4.9.0:
1
Test_t* self = this;
2
typeBasedDispatch <TypeA_t, TypeB_t> (base_type, [=] (auto& obj) { self->OnType (obj); });

von CppBert (Gast)


Lesenswert?

klappt wunderbar - sogar mit dem etwas in die Jahre gekommenen VS2015 - 
super Lösung Danke

von Wilhelm M. (wimalopaan)


Lesenswert?

Bei solchen Fragen kann ich Dir eigentlich nur raten, diese auf 
stackoverflow zu stellen.

Dieses Forum hat ja den Titel Mikrocontroller.net, es sind hier 
demzufolge viele Leute unterwegs, die (unverständlicherweise) mit C++ 
nichts am Hut haben und schon gar nicht mit generischem Code, und eher 
weniger die kompetenten C++-Experten.

Niklas G. ist sicher eine große Ausnahme, er hat sich viel mehr Mühe 
gegeben, Dein Problem zu verstehen als ich, und er hat Dir sehr gut 
weitergeholfen. Das ist auf diesem Niveau hier aber sehr selten ...

von CppBert (Gast)


Lesenswert?

>Bei solchen Fragen kann ich Dir eigentlich nur raten, diese auf
>stackoverflow zu stellen.

oder reddit/cpp_question, cppforum, ... ich bin auf ein paar Platformen 
Unterwegs - wollte man ausprobieren wie die Mikrocontroller.net 
Community reagiert :)

>Dieses Forum hat ja den Titel Mikrocontroller.net, es sind hier
>demzufolge viele Leute unterwegs, die (unverständlicherweise) mit C++
>nichts am Hut haben und schon gar nicht mit generischem Code, und eher
>weniger die kompetenten C++-Experten.

Das ist hier gar nicht so das Problem - nach meiner Erfahrung - eher das 
sich hier viel zu viele "Profis" mit Halbwissen dauerhaft Unterhaltungen 
einnehmen - einer schreibt eine etwas schlecht formulierte Frage und 10 
Mann schlagen sich dann darum wer die blöderen 08/15 Kommentare abgibt - 
meist in vollkommener Abwesenheit der Threadstarters...

>Niklas G. ist sicher eine große Ausnahme, er hat sich viel mehr Mühe
>gegeben, Dein Problem zu verstehen als ich, und er hat Dir sehr gut
>weitergeholfen. Das ist auf diesem Niveau hier aber sehr selten ...

wenn man sich keine Mühe gibt das Problem zu verstehen sollte man nicht 
Antworten - wenn man nichts sinnvolles Beitragen kann sollte man nicht 
Antworten - das sind Regeln die leider zu viele im Internet nicht 
verstehen

Danke noch mal an Niklas G. der mit seiner Erfahrung recht schnell mein 
Problem verstanden hat und auch lösen konnte - Danke auch den anderen 
Beteiligten welche die Posts hier warm gehalten haben :)

von Cyblord -. (Gast)


Lesenswert?

CppBert schrieb:
> oder reddit/cpp_question, cppforum, ... ich bin auf ein paar Platformen
> Unterwegs - wollte man ausprobieren wie die Mikrocontroller.net
> Community reagiert :)

Anders formuliert, du wolltest mal ein bisschen mit erweiterten 
C++-Kenntnissen angeben.
Damit exponierst du dich auf eine Stufe wie jemand der im 
Modelleisenbahnforum seine Landschaftsgärtnerkenntnisse zur Schau 
stellen will.

von Cppbert (Gast)


Lesenswert?

Abradolf L. schrieb:
> Anders formuliert, du wolltest mal ein bisschen mit erweiterten
> C++-Kenntnissen angeben.
> Damit exponierst du dich auf eine Stufe wie jemand der im
> Modelleisenbahnforum seine Landschaftsgärtnerkenntnisse zur Schau
> stellen will.

Ja richtig - genau so wie das kleine Kinder eben machen :)

Falls es dir aufgefallen ist: mein Anfangs Post hier ist 08/15 C++ - die 
"erweiterten C++-Kenntnissen" kamen von "Niklas G."

und @Wilhelm M.

Es gibt hier recht viele gute, und auch schnell reagierende C++ 
Entwickler - es gibt also keine Notwendigkeit die C++ "Hilfe-Performanz" 
in dieser Community klein zu reden :)

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.