Forum: PC-Programmierung C++ Template spezialisation einer member function


von Da D. (dieter)


Lesenswert?

Moin zusammen,

Normalerweise kann man ja eine Memberfunktion, die eine const referrence 
als parameter bekommt, problemlos mit einem non-const object aufrufen, 
da der cast non-const -> const immer möglich ist:
1
class ClassA
2
{
3
    void f(const ClassB& b);
4
}
5
...
6
7
ClassA a;
8
ClassB b;
9
a.f(b);  //b ist nicht const, macht aber nichts

So weit so gut. Wenn ich aber jetzt ein template aus f mache gibts ein 
Problem:
1
class ClassA
2
{
3
    template<typename T>
4
    void f(T& t) {...};
5
    template<>
6
    void f(const ClassB& b) {...};
7
}
8
...
9
10
ClassA a;
11
ClassB b;
12
a.f(b);  //b ist nicht const, und deswegen funktioniert die spezialisierung nicht :-/

Jetzt wird die Funktion für ClassB plötzlich nicht mehr aufgerufen, 
sondern die generische. So weit ich weiß wird doch eigentlich immer die 
am weitesten spezialisiere Funktion, die auf den übergebenen Typ passt, 
aufgerufen. Und passen tut es, denn ohne template gehts ja auch.

Mach ich irgendetwas falsch? Oder muss ich jetzt echt für "ClassB&" und 
"const ClassB&" seperate spezialisierungen machen? Oder gibts nen 
besseren Weg?

von Sebastian V. (sebi_s)


Lesenswert?

Ja so ist das nunmal mit den C++ Overload Regeln. Kein Cast ist besser 
als ein const hinzufügen also kriegst du die Funktion mit dem Template. 
Eine einfache Möglichkeit das zu umgehen ist bei der Template Variante 
auch ein const hinzuzufügen (falls der Code der Funktion das 
ermöglicht). Dann erfordern beiden Funktionen den gleichen Cast aber die 
Variante ohne Template "gewinnt".

von Karl H. (kbuchegg)


Lesenswert?

Da D. schrieb:


> aufgerufen. Und passen tut es, denn ohne template gehts ja auch.

Auch ohne Template würde bei
1
class ClassA
2
{
3
     void f(ClassB& b);
4
     void f(const ClassB& b);
5
}

die non-const Version bevorzugt werden.

Man kann sich das auch so merken: die const-Version wird nur dann 
benutzt, wenn es unbedingt sein muss, weil das Argument des Aufrufers 
selber const ist.

ein 'const' in einer Argumentschnittstelle ist so zu lesen: Die Funktion 
macht die Zusicherung, dass es das übergebene Argument nicht verändern 
wird.

Logischerweise passt ein derartiges const natürlich auch dann, wenn der 
Aufrufer ein Objekt hat, dass veränderbar wäre. Macht ja nix, wenn die 
Funktion dieses Objekt nicht verändern will. Sie könnte - aber wenn sie 
es nicht tut, ist es auch gut.
Aber umgekehrt geht das natürlich nicht. Wenn der Aufrufer ein Objekt 
hat, dass nicht verändert werden darf, dann muss die Funktion auch die 
entsprechende Zusicherung machen.

>
> Mach ich irgendetwas falsch? Oder muss ich jetzt echt für "ClassB&" und
> "const ClassB&" seperate spezialisierungen machen? Oder gibts nen
> besseren Weg?

Kommt auf die Funktion an. Wenn f das Objekt sowieso nicht verändern 
will, dann kann man natürlich in der nicht-const Version das const 
dazucasten und die const-Version aufrufen. Macht man das alles inline, 
dann kostet das auch keine Laufzeit.

von Da D. (dieter)


Lesenswert?

Sebastian V. schrieb:
> Eine einfache Möglichkeit das zu umgehen ist bei der Template Variante
> auch ein const hinzuzufügen

Gute Idee. Aber leider muss die template Variante das Objekt verändern 
können. Bzw. ich kann nur für die Spezialisierung zusichern, dass nichts 
verändert wird.

Karl H. schrieb:
> Auch ohne Template würde bei class ClassA
> die non-const Version bevorzugt werden.

Hm, ja, das erklärt es wohl. Der cast auf const wird nur gemacht, wenn 
keine direkt passende Funktion gefunden wird. Und ein template, bei dem 
alles erlaubt ist, passt halt für alles...

Karl H. schrieb:
> Wenn f das Objekt sowieso nicht verändern
> will, dann kann man natürlich in der nicht-const Version das const
> dazucasten und die const-Version aufrufen.

Ja, so mach ich es dann wohl. In der spezialisierten Variante muss ich 
das Objekt nicht verändern. Also rufe ich, um Codeduplikation zu 
vermeiden,in der nicht-const Version die const Version auf.

Danke euch!

von Karl H. (kbuchegg)


Lesenswert?

Da D. schrieb:

> Also rufe ich, um Codeduplikation zu
> vermeiden,

Genau darum gehts.

Wenn man den Cast vermeiden will, geht immer noch
1
class ClassA
2
{
3
...
4
     void f(ClassB& b)           { f_impl( b ); }
5
     void f(const ClassB& b)     { f_impl( b ); }
6
7
private:
8
     void f_impl( const ClassB& b );
9
};

: Bearbeitet durch User
von Sebastian V. (sebi_s)


Lesenswert?

Vielleicht etwas übertrieben aber man kann auch mit SFINAE was zaubern 
um die Template Version für ClassB zu deaktivieren:
1
class ClassA
2
{
3
    template<typename T, typename = std::enable_if_t<!std::is_same<T, ClassB>::value>>
4
    void f(T& t) {}
5
    
6
    void f(const ClassB& b) {}
7
};
Wenn noch mehr Spezialisierungen dazu kommen ist das aber auch nicht 
gerade so schön.

von apr (Gast)


Lesenswert?

Fehlt da nicht ein std::decay?

von Da D. (dieter)


Lesenswert?

Sebastian V. schrieb:
> man kann auch mit SFINAE was zaubern

Genial :) So funktioniert es auch. Wieder was gelernt. Danke!

apr schrieb:
> Fehlt da nicht ein std::decay?

In meinem speziellen Fall geht es tatsächlich auch ohne. Für "const 
ClassB" ist ja schon eine Spezialisierung vorhanden. Für "ClassB" greift 
dann die Bedingung in std::is_same.

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.