Forum: PC-Programmierung C++ Template-Methode const und nicht-const


von Tom (Gast)


Lesenswert?

/*
Vorgeschichte
Eine Klasse enthält eine Datenstruktur, über die in verschiedenen 
Methoden auf ähnliche Weise in bestimmten Mustern iteriert werden muss.
Da diese Iteration relativ kompliziert ist und definitiv nicht per 
Copy+Paste in die einzelnen Methoden gehört, wurde sie in eine 
Template-Methode ausgelagert, die mit der Operation als Parameter 
aufgerufen wird (In diesem Fall Hipster-Lambdas, Functoren gingen auch, 
std::function hat Overhead, der hier wahrscheinlich stören würde).

Das Minimalbeispiel zeigt das Problem. In der echten Anwendung ist 
natürlich alles komplizierter und das beschriebene Pattern war die 
lesbarste und sauberste Lösung, die performant bleibt.

Eine const-Methode (1) kann natürlich keine nicht-const-Methode (3) 
aufrufen und umgekehrt (2) (4), weshalb die Template-Methode in beiden 
Varianten nötig scheint (const overloading)
Es wäre schöner, wenn -- naiv ausgedrückt --  irgendwie automagisch 
anhand dessen, was der zweite Parameter tut, entschieden würde, ob die 
Template-Methode const ist oder nicht.


Frage
Gibt es eine Möglichkeit, die beiden Kopien von for_every_nth, die sich 
nur durch das const unterscheiden, irgendwie wie beschrieben 
zusammenzufassen? Es sollte weder langsamer werden
noch viel Code erfordern (Selbst implementierte Spezial-Iteratoren sind 
damit ausgeschlossen). Boost auf Expertenniveau möchte ich auch 
vermeiden, dann behalte ich lieber die aktuelle Copy&Paste-Lösung.
*/
//
1
#include <iostream>
2
#include <array>
3
4
class Foo
5
{
6
public:
7
    Foo()
8
    {
9
        numbers.fill(12);
10
    }
11
        
12
    void print_even_tens() const                        /* (1) */
13
    {
14
        auto print_if_even = [](const int& x)
15
        {
16
            if (x % 2 == 0)
17
                std::cout << x << " gerade\n";
18
        };
19
        for_every_nth(10, print_if_even);
20
    }
21
22
    void fill_em_all()                                  /* (2) */
23
    {
24
        int i = 16;
25
        auto set_rising = [&i](int& x)
26
        {
27
            x = i;
28
            i++;
29
        };
30
        for_every_nth(1, set_rising);
31
    }
32
33
private:
34
    std::array<int, 42> numbers;
35
36
37
    template <typename T>
38
    void for_every_nth(size_t step, T& doit)     /* (3) */
39
    {
40
        for (size_t i = 0; i < numbers.size(); i += step)
41
            doit(numbers[i]);
42
    }
43
44
45
    template <typename T>
46
    void for_every_nth(size_t step, T& doit) const /* (4) */ 
47
    {
48
        for (size_t i = 0; i < numbers.size(); i += step)
49
            doit(numbers[i]);
50
    }
51
};
52
53
int main()
54
{
55
    Foo foo;
56
    foo.print_even_tens();
57
    foo.fill_em_all();
58
    foo.print_even_tens();
59
}
60
//

von Sebastian V. (sebi_s)


Lesenswert?

Muss das for_every_nth eine normale Memberfunktion sein? Man könnte auch 
eine statische Memberfunktion nehmen wo man das Array separat übergibt:
1
  template <typename T, typename U>
2
  static void for_every_nth(size_t step, T& doit, U& numbers)
3
  {
4
    for(size_t i = 0; i < numbers.size(); i += step)
5
      doit(numbers[i]);
6
  }

von Mikro 7. (mikro77)


Lesenswert?

Du implementierst die const Methode.

Die non-const Methode ruft die const Methode auf, mit entsprechenden 
const casts.

Bsp: 
http://stackoverflow.com/questions/123758/how-do-i-remove-code-duplication-between-similar-const-and-non-const-member-func

Nicht schönt. Scheint aber die akzeptierte Vorgehensweise zu sein.

von Sebastian V. (sebi_s)


Lesenswert?

Mikro 7. schrieb:
> Die non-const Methode ruft die const Methode auf, mit entsprechenden
> const casts.

Ist bei ihm aber leicht anders, da er keinen Rückgabewert hat sondern 
mit der Lambda Funktion Zeug erledigen will. So könnte man es machen, 
obwohl ich das nicht wirklich schön finde:
1
  template <typename T>
2
  void for_every_nth(size_t step, T& doit)
3
  {
4
    auto doit_non_const = [&doit](auto& x){
5
      doit(const_cast<std::remove_const_t<std::remove_reference_t<decltype(x)>>&>(x));
6
    };
7
    static_cast<const Foo*>(this)->for_every_nth(step, doit_non_const);
8
  }

von Mikro 7. (mikro77)


Lesenswert?

Sebastian V. schrieb:
> Ist bei ihm aber leicht anders...

Yo. Was ist denn die Signature der zweiten Lambda Funktion (set_rising)?

Bei der ersten ist es klar. Da paßt:
1
void for_every_nth(size_t step, void(*doit)(int const &x)) const

Aber bei der zweiten funzt (überraschend?) nicht:
1
void for_every_nth(size_t step, void(*doit)(int &x))

...hätte nicht gedacht dass [&i] die Signatur ändert.

(Ich wollte gerade Mal die Templates auseinandernehmen...)

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Mikro 7. schrieb:
> ...hätte nicht gedacht dass [&i] die Signatur ändert.

Es ist nicht die capture expression, sondern die Parameter Liste. Die 
eine Funktion nimmt (int& x) und die andere (const int& x). Und die 
erste Funktion kann die nicht const qualifizierte Version von 
for_every_nth () halt nicht aufrufen.

Wenn die Funktion so übersichtlich, wie im Beispiel ist, würde ich sie 
zwei mal ausschreiben, ansonsten würde ich (wie von Dir vorgeschlagen) 
mit const_cast auf die non-const Version verweisen:
1
    template < typename T >
2
    void for_every_nth( std::size_t step, T& doit ) const
3
    {
4
        const_cast< Foo& >( *this ).for_every_nth( step, doit );
5
    }

von Sebastian V. (sebi_s)


Lesenswert?

Torsten R. schrieb:
> Wenn die Funktion so übersichtlich, wie im Beispiel ist, würde ich sie
> zwei mal ausschreiben, ansonsten würde ich (wie von Dir vorgeschlagen)
> mit const_cast auf die non-const Version verweisen:

Wenn du das so rum machst kann man mit der const Version auch Variablen 
verändern, je nachdem was die Lambda Funktion macht.

von Mikro 7. (mikro77)


Lesenswert?

Moin

Sebastian V. schrieb:
> So könnte man es machen

Nach dem Ausschlafen sieht das jetzt gut aus. :-)

Mein Compiler mag allerdings kein "auto" im Lambda-Kopf (C++14?). Also 
bspw:
1
template <typename T> void for_every_nth(size_t step, T& doit)
2
{
3
  auto doit_non_const = [&doit](int const &x){
4
    doit(const_cast<int&>(x)) ;
5
  } ; 
6
  static_cast<Foo const*>(this)->for_every_nth(step,doit_non_const);
7
}

Ich schrieb:
> Was ist denn die Signature der zweiten Lambda Funktion (set_rising)?

Ist nix mit Funktionspointer. Funktionieren nur bei Lambdas ohne 
Captures.

Hier ein netter Artikel zu Lambdas: 
https://blog.feabhas.com/2014/03/demystifying-c-lambdas/

von Mikro 7. (mikro77)


Lesenswert?

Sebastian V. schrieb:
> Torsten R. schrieb:
>> Wenn die Funktion so übersichtlich, wie im Beispiel ist, würde ich sie
>> zwei mal ausschreiben, ansonsten würde ich (wie von Dir vorgeschlagen)
>> mit const_cast auf die non-const Version verweisen:
>
> Wenn du das so rum machst kann man mit der const Version auch Variablen
> verändern, je nachdem was die Lambda Funktion macht.

Habe mich auch gefragt, was alles schief gehen kann, wenn man es 
umgekehrt macht. Habe den Scott Meyer nur in der 2nd ed. Da steht das 
noch nicht drin.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Sebastian V. schrieb:
> Torsten R. schrieb:
>> Wenn die Funktion so übersichtlich, wie im Beispiel ist, würde ich sie
>> zwei mal ausschreiben, ansonsten würde ich (wie von Dir vorgeschlagen)
>> mit const_cast auf die non-const Version verweisen:
>
> Wenn du das so rum machst kann man mit der const Version auch Variablen
> verändern, je nachdem was die Lambda Funktion macht.

Könnte man..., es ist aber eine private Funktion. Wenn die Klasse so 
unübersichtlich ist, dass die Gefahr besteht, dass der Autor dort in ein 
const qualifizierten Funktion ein Lambda verwendet, dass ein int& nimmt, 
dann kann man natürlich das Lambda Argument zu compile-Zeit auch noch 
mal prüfen.

: Bearbeitet durch User
von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

so nicht! schrieb im Beitrag #4602528:
> Immer wieder lustig, wenn so ein Möchtegernprogrammieren eine Abhandlung
> nach dem Schema

Und vielleicht auch einfach noch mal umdrehen, wenn man Sonntag-Morgen 
schlechte Laune hat...

von Tom (Gast)


Lesenswert?

Danke für alle Beiträge und sorry für die späte Rückmeldung!

Sebastian V. schrieb:
> statische Memberfunktion
Das wäre genau die einfache Lösung gewesen, die ich gesucht habe. Leider 
muss auf einige Membervariablen/-methoden zugegriffen werden. Das kann 
man zwar irgendwie mitübergeben, aber es wird dann noch hässlicher als 
die doppelte Methode.

so nicht! schrieb im Beitrag #4602528:
> aufrufen und umgekehrt (2) (4), weshalb die Template-Methode in beiden
>> Varianten nötig scheint (const overloading)
>
> Schwachsinn. Selbstverständlich kann eine non-const-Methode eine
> const-Methode aufrufen.

Danke für die Korrektur. Der zweite Halbsatz war in dieser Form 
natürlich Unfug und hätte heißen müssen: "...und umgekehrt kann eine 
nicht-const-Methode nicht die const-Template-Methode mit einem 
datenverändernden Lambda aufrufen, weshalb...". Das löst aber das 
ursprüngliche Problem nicht.

Die Cast-Ansätze werde ich in Ruhe analysieren.

so nicht! schrieb im Beitrag #4602528:
> wie es jeder andere seit 100 Jahren macht.
Was wäre denn der bewährte Weg in diesem Fall? Der, den ich kenne, 
verzichtet einfach ganz auf const-correctness.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Tom schrieb:
> Die Cast-Ansätze werde ich in Ruhe analysieren.

BTW: Gibt es einen guten Grund, warum der Parameter doit per none 
const reference übergeben wird?

von Tom (Gast)


Lesenswert?

Das ist (soweit ich weiß) nötig, wenn man einen guten alten Functor, 
dessen operator() nicht const ist,  übergeben will:
1
class Summer
2
{
3
private:
4
    int sum;
5
public:    
6
    Summer() : sum(0) 
7
    {
8
    }
9
    
10
    void operator()(const int& x)
11
    {
12
        sum += x;
13
    }
14
    
15
    int get_result() const
16
    {
17
        return sum;
18
    }
19
};
20
21
22
class Foo
23
{
24
//...
25
    void calc_sum()
26
    {
27
        Summer summer;
28
        for_every_nth(3, summer);
29
        std::cout << summer.get_result();
30
    }
31
//...
32
};

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.