Forum: PC-Programmierung C++11 return typ automatisch bestimmen


von Template Neuling (Gast)


Lesenswert?

Hallo Leute,

ich möchte gerne für eine C++ member funktion den return Wert 
automatisch bestimmen.
Konkret habe ich eine template Klasse, die einen Punkt definiert. (Diese 
hier, nur ein wenig erweitert: 
http://stackoverflow.com/questions/11891192/c-templates-simple-point-class?answertab=votes#tab-top)

Ich würde gerne zwei Punkte mit unterschiedlichen typen addieren (z.B. 
float und int), wobei der cast automatisch ausgeführt werden soll (wie 
beim normalen addieren.

Ich habe das mal so runtergeschrieben:
1
    template<typename newTypeT>
2
    friend Point<newTypeT, dimensionsCount> operator+(Point const& a, Point const& b)
3
    {
4
        auto tmp = a[0] * b[0];
5
6
        Point<decltype(tmp), dimensionsCount> ret();
7
        for (int i = 0; i < dimensionsCount; ++i) {
8
            ret[i] = a[i] * b[i];
9
        }
10
11
        return ret;
12
    }

Leider funktioniert das so nicht richtig.
Hat jemand einen Tipp, wie das richtig gelöst werden kann?

von tictactoe (Gast)


Lesenswert?

Template Neuling schrieb:
> Leider funktioniert das so nicht richtig.

Definiere "funktioniert so nicht richtig".

Nebenbei erwähnt ist das hier:

       Point<decltype(tmp), dimensionsCount> ret();

die Deklaration einer Funktion, die keine Parameter nimmt und einen 
Point<...> zurückgibt. So geht's besser:

       Point<decltype(tmp), dimensionsCount> ret{};

von Daniel A. (daniel-a)


Lesenswert?

Wenn ich dich richtig verstehe, willst du quasi einen Operator der etwa 
so wie f in diesem Beispiel reagiert:
1
int f();
2
float f();
3
int x = f();
4
float y = f();
Soetwas ist nicht möglich, egal ob Operator oder Funktion, egal ob mit 
oder ohne Templares. Die rückgabewerte sind meineswissens nicht teil der 
Funktionssignatur, der Compiler kann sie nicht unterscheiden und kann 
nicht die richtige wählen.

Was jedoch möglich sein sollte ist sowas: (ungetestet)
1
template<typename T>
2
class X {
3
  template<typename U>
4
  T operator[](unsigned i){
5
    return 0;
6
  }
7
  template<typename U>
8
  auto operator+(X const& a, X<U> const& b) -> decltype(a[0]+b[0]) {
9
        X<decltype(a[0]+b[0]))> ret;
10
11
        return ret;
12
    }
13
};

Hierbei ist der rückgabewert von den Typen der Argumente abhängig, 
wodurch die Funktionssignatur immer eindeutig ist.

tictactoe schrieb:
> Nebenbei erwähnt ist das hier:
>
>        Point<decltype(tmp), dimensionsCount> ret();
>
> die Deklaration einer Funktion

In C++ ist das auch eine valide Definition samt Initialisierung. Das 
sollte schon stimmen, mochte ich auch nie.

: Bearbeitet durch User
von Arc N. (arc)


Lesenswert?

Daniel A. schrieb:
> Wenn ich dich richtig verstehe, willst du quasi einen Operator der etwa
> so wie f in diesem Beispiel reagiert:
>
1
> int f();
2
> float f();
3
> int x = f();
4
> float y = f();
5
>
> Soetwas ist nicht möglich, egal ob Operator oder Funktion, egal ob mit
> oder ohne Templares. Die rückgabewerte sind meineswissens nicht teil der
> Funktionssignatur, der Compiler kann sie nicht unterscheiden und kann
> nicht die richtige wählen.

Warum sollte es nicht?
1
template <class Type> auto f1(Type x) {
2
  return x + (Type)1;
3
}
4
5
struct TestStruct {
6
    double getVal() { return 4.4; }
7
};
8
9
template <class Type> auto operator+(TestStruct a, Type b) {
10
    return b + (Type)a.getVal();
11
}
12
13
int main() {
14
    std::cout << f1(3.141) << "\t" << f1(1) << std::endl;
15
    
16
    TestStruct test;
17
    std::cout << test + 1.4 << "\t" << test + 3 << std::endl;
18
    
19
    return 0;
20
}

von Daniel A. (daniel-a)


Lesenswert?

@Arc Net (arc)

Betrachte mein 1. Beispiel und deins nochmal ganz genau. Bei meiner 
Aussage & Beispiel geht es um Funktionen oder Operatoren mit identischem 
Namen und Argumenten aber unterschiedlichem Return type im selben Scope. 
Meine Aussage bezog sich explizit auf mein Beispiel. Dein Beispiel zeigt 
eine ganz andere Ausganslage. Ausserdem, was war an der Erklärung 
unklar?

von Karl H. (kbuchegg)


Lesenswert?

Daniel A. schrieb:
9573:

>> Nebenbei erwähnt ist das hier:
>>
>>        Point<decltype(tmp), dimensionsCount> ret();
>>
>> die Deklaration einer Funktion
>
> In C++ ist das auch eine valide Definition samt Initialisierung. Das
> sollte schon stimmen, mochte ich auch nie.

Bist du dir da sicher?
In C++ gibt es eine Mehrdeutigkeit
1
  obj foo();

das könnte sein
* der Funktionsprotoyp einer Funktion foo, die keine Argumente nimmt und 
ein obj Objekt zurückliefert
* oder die Definition einer Variablen foo, die vom Typ obj ist und die 
zur Initialisierung mit dem Default-Constructor vorgesehen ist.

Meines Wissens wird hier so vorgegangen:
If it looks like a function declaration, then it is one.
1
class obj
2
{
3
  public:
4
    obj()    {}
5
6
    int a;
7
};
8
9
int main()
10
{
11
  obj foo();
12
13
  foo.a = 5;     // error C2228: left of '.a' must have class/struct/union
14
15
  return 0;
16
}


Ich hab mir angewöhnt, bei Objekten für die ich den Default Konstruktor 
haben möchte, ganz einfach keine Initialisierungsklammern zu schreiben. 
Den subtilen Unterschied mit der Zero-Initialisierung ignoriere ich bzw. 
den nutze ich nicht aus.
1
  obj foo;
dann ist alles eindeutig.

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


Lesenswert?

Ich habe Dein Beispiel mal verkürzt, der Return-Wert lässt sich in C++11 
über ein decltype relativ bequem ermitteln (in C++14 könntest Du ihn weg 
lassen; das wäre dann das Beispiel von Arc Net):
1
template < typename T >
2
struct Point {
3
    T operator[](int) const;
4
};
5
6
template < typename A, typename B >
7
auto operator+(const Point<A>& a, const Point<B>& b ) -> Point< decltype( A() * B() ) >
8
{
9
    auto val = a[0] * b[0];
10
11
    return Point< decltype(val) >();
12
}
13
14
int main()
15
{
16
    Point< int > a;
17
    Point< double > b;
18
19
    a + b;
20
}

mfg Torsten

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Karl H. schrieb:
> Ich hab mir angewöhnt, bei Objekten für die ich den Default Konstruktor
> haben möchte, ganz einfach keine Initialisierungsklammern zu schreiben.
> Den subtilen Unterschied mit der Zero-Initialisierung ignoriere ich bzw.
> den nutze ich nicht aus.
>  obj foo;
> dann ist alles eindeutig.

Oben wurde schon die seit C++11 (zumindest laut Stroustrup) zu 
bevorzugende Variante genannt:
1
obj foo{};

Damit sind auch keine Mehrdeutigkeiten vorhanden.

von apr (Gast)


Lesenswert?

Daniel A. schrieb:
> In C++ ist das auch eine valide Definition samt Initialisierung. Das
> sollte schon stimmen, mochte ich auch nie.

Nope. Das ist eine Funktionsdeklaration.

Oder um es mit den Worten des Compilers zu sagen:
1
objvsfunc.cpp:5:9: warning: empty parentheses interpreted as a function declaration [-Wvexing-parse]
2
        obj foo();
3
               ^~
4
objvsfunc.cpp:5:9: note: remove parentheses to declare a variable
5
        obj foo();
6
               ^~
7
1 warning generated.
bzw.: https://en.wikipedia.org/wiki/Most_vexing_parse

von Template Neuling (Gast)


Angehängte Dateien:

Lesenswert?

Erstmal vielen Dank für die vielen Antworten!

Ich habe das mal versucht für meinen Fall anzupassen (ich habe noch die 
Anzahl der Dimensionen, die der Punkt speichert als Template parameter. 
(Es macht in meinen Anwendungen nur Sinn Punkte gleicher 
Dimensionsanzahl mit einander zu addieren.) )
1
template<typename A, typename B>
2
    auto operator+(const Point<A, dimensionsCount>& a, const Point<B, dimensionsCount>& b) -> Point< decltype( A() + B() ) , dimensionsCount>
3
    {
4
        auto val = a[0] + b[0];
5
6
        Point<decltype(val), dimensionsCount> retPoint { };
7
8
        for (int i = 0; i < dimensionsCount; ++i) {
9
            retPoint[i] = a[i] + b[i];
10
        }
11
12
        return Point<decltype(val)>();
13
    }

Allerdings wird hier dieser Fehler angezeigt:
" error: 'Point<decltype ((A() + B())), dimensionsCount> Point<T, 
dimensionsCount>::operator+(const Point<A, dimensionsCount>&, const 
Point<B, dimensionsCount>&)' must take either zero or one argument"

Ich kann mit dem Fehler nichts anfangen, denn der '+' Operator sollte 
doch zwei Argumente erwarten.
Wo liegt hier das Problem?


Ich habe die Ponit.h mal angehängt, falls jemand reinschauen möchte.

von Hannes W. (7nrn3ap3)


Lesenswert?

1
#include <type_traits>
2
template<typename LHS, typename RHS, size_t /* ? */ dimCount, typename Result = typename std::common_type<LHS, RHS>::type>
3
friend Point<Result, dimCount> operator+(Point<LHS, dimCount> const& a, Point<RHS, dimCount> const& b) {
4
  /* … */
5
}

Mit auto return type deduction hat das überhaupt nichts zu tun. Dazu 
müsste man schon 'friend auto …' oder 'friend decltype(auto) …' (C++14) 
schreiben. Würde dir aber auch nichts nützen. Denn den Typ der Variable 
die du zurückgeben willst brauchst du bei deren Deklaration sowieso. Man 
spart sich den Typ halt einmal:
1
template<typename LHS, typename RHS, size_t /* ? */ dimCount, typename Result = typename std::common_type<LHS, RHS>::type>
2
friend auto operator+(Point<LHS, dimCount> const& a, Point<RHS, dimCount> const& b) {
3
  Point<Result, dimCount> tmp;
4
  /* … */
5
  return tmp;
6
}

Damit umgehst du auch gleich das Problem Punkte mit unterschiedlicher 
Dimensionalität zu verknüpfen. Den operator[] zu überladen um den in 
decltype zu klatschen halte ich für höchst fragwürdig. common_type wurde 
für dieses Szenario gemacht und sollte hier verwendet werden.

In C++14 kann man dann auch std::common_type_t<...> benutzen und spart 
sich ein 'typename' und ein '::type'.

von Hannes W. (7nrn3ap3)


Lesenswert?

Template Neuling schrieb:

> Ich kann mit dem Fehler nichts anfangen, denn der '+' Operator sollte
> doch zwei Argumente erwarten.
> Wo liegt hier das Problem?

Kein 'friend' und das Ding inline definiert. Entweder du hast einen 
Parameter (die rechte Seite des +-Ausdrucks) und 'this' ist die linke 
Seite, oder du deklarierst das als 'friend' und musst sowohl linke als 
auch rechte Seite als Parameter übergeben.

von Template Neuling (Gast)


Lesenswert?

Das mit common_type zu lösen ist natürlich viel schöner! Danke dafür!


Hannes W. schrieb:
> size_t /* ? */ dimCount
Ich hab das mal auf int gesetzt. size_t ist ja eher auf Speicher 
bezogen. Bei mir geht es sowieso nur um wenige Dimensionen.


Ich habe die Lösung
1
template<typename LHS, typename RHS, int dimCount, typename Result = typename std::common_type<LHS, RHS>::type>
2
    friend auto operator+(Point<LHS, dimCount> const& a, Point<RHS, dimCount> const& b)
3
    {
4
        Point<Result, dimCount> tmp;
5
        /* … */
6
        return tmp;
7
    }
mal eingepflegt, allerdings schmeißt der Compiler den Fehler "non-static 
data member declared 'auto'".
Warum denn 'data member', es ist doch eine Funktion?

von Template Neuling (Gast)


Lesenswert?

Hannes W. schrieb:
> Kein 'friend' und das Ding inline definiert.

Einleuchtend!

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


Lesenswert?

Template Neuling schrieb:
> Ich habe die Lösung
> mal eingepflegt, allerdings schmeißt der Compiler den Fehler "non-static
> data member declared 'auto'".

Wenn Du wirklich C++11 meinst, dann must Du den Return-Typen der 
Funktion angeben:
1
template<typename LHS, typename RHS, int dimCount >
2
auto operator+(Point<LHS, dimCount> const& a, Point<RHS, dimCount> const& b) -> Point< typename std::common_type<LHS, RHS>::type, dimCount >
3
{
4
    Point<typename std::common_type<LHS, RHS>::type, dimCount > tmp;
5
    /* … */
6
    return tmp;
7
}

von Klaus W. (mfgkw)


Lesenswert?

Nur mal am Rand: was soll eigentlich rauskommen, wenn man zwei Punkte 
addiert? Ein Doppelpunkt?

von Hannes W. (7nrn3ap3)


Lesenswert?

Template Neuling schrieb:
> Hannes W. schrieb:
>> size_t /* ? */ dimCount
> Ich hab das mal auf int gesetzt. size_t ist ja eher auf Speicher
> bezogen. Bei mir geht es sowieso nur um wenige Dimensionen.
Aha. Und was hat eine Dimension von -2 für eine Semantik? Dann nimm 
wenigstens unsigned. Ich persönlich würde immer die Bitbreite des Typs 
explizit angeben, z.B. uint32_t, damit funktioniert es auf allen 
Plattformen. size_t bietet sich deshalb an, da du ja intern ein 
Array/Vector benutzt, dessen Größe (size_type) was ist? size_t.

Torsten R. schrieb:
>> mal eingepflegt, allerdings schmeißt der Compiler den Fehler "non-static
>> data member declared 'auto'".
>
> Wenn Du wirklich C++11 meinst, dann must Du den Return-Typen der
> Funktion angeben:
Äh ja, sorry. Ich benutzte kein C++11, nur C++14/1z :)

Klaus W. schrieb:
> Nur mal am Rand: was soll eigentlich rauskommen, wenn man zwei
> Punkte
> addiert? Ein Doppelpunkt?
Einen Punkt kann man immer auch als einen Ursprungsvektor auffassen. 
Damit wird's eine Vektoraddition. Die Implementation von operator+ im 
ersten Post implementiert allerdings das Hadamard-Produkt. Wenn der TO 
den Code bei mir abliefern würde …

: Bearbeitet durch User
von Klaus W. (mfgkw)


Lesenswert?

Hannes W. schrieb:
> Einen Punkt kann man immer auch als einen Ursprungsvektor auffassen.

Genau darauf wollte ich raus...

Punkte zu addieren ist Blödsinn; wenn man Vektoren meint, sollte man sie 
auch so nennen.

von Sebastian V. (sebi_s)


Lesenswert?

Template Neuling schrieb:
> Das mit common_type zu lösen ist natürlich viel schöner! Danke
> dafür!

Wobei man noch anmerken sollte, dass man mit common_type den Typ kriegt 
in den man beiden Typen konvertieren kann. Für eigene Typen ist das 
nicht zwingend der Typ der bei einer Addition/Multiplikation rauskommt. 
Für Builtin Types passt es aber.

von Daniel A. (daniel-a)


Lesenswert?

Klaus W. schrieb:
> Punkte zu addieren ist Blödsinn; wenn man Vektoren meint, sollte man sie
> auch so nennen.

Ich finde es gut und Praktisch, wenn Punkte addiert, multipliziert, etc. 
werden können. Wer dabei lieber an Vektoren denkt kann ja "using" 
verweden:
1
 // Ungetestet
2
template<typename T, int dimensionsCount>
3
using Vector = Point<T,dimensionsCount>;

Um mal einige Usecases für Punktaditionen zu bringen:

Mittelpunkt Berechnen:
1
// Ungetestet
2
Point<float,2> points[] = {
3
  Point<float,2>( -1,  0 ),
4
  Point<float,2>(  1,  0 ),
5
  Point<float,2>(  0, -1 ),
6
  Point<float,2>(  0,  1 )
7
};
8
Point<float,10> center { 0, 0 };
9
10
for( auto it=points, end=points+sizeof(points)/sizeof(*points); it < end; it++ )
11
  center += *it;
12
13
center /= sizeof(points)/sizeof(*points);

Lineare Interpolation:
1
  float t = 0.5; // Position zwischen den Punkten  0 <= t <= 1 
2
  Point<float,2> a { -1,  1 };
3
  Point<float,2> b {  1, -1 };
4
  auto result = a*(1-t) + b*t;

: Bearbeitet durch User
von Sebastian V. (sebi_s)


Lesenswert?

Daniel A. schrieb:
> Wer dabei lieber an Vektoren denkt kann ja "using"
> verweden

Ich denk dabei auch immer an Vektoren. Punktaddition habe ich noch nie 
gehört (Google spuckt dabei einen haufen Ergebnisse zu elliptischen 
Kurven aus).

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Sebastian V. schrieb:
> Daniel A. schrieb:
>> Wer dabei lieber an Vektoren denkt kann ja "using"
>> verweden
>
> Ich denk dabei auch immer an Vektoren. Punktaddition habe ich noch nie
> gehört.

Geht mir auch so. Und Multiplikation schon gar nicht. Man kann einen 
Vektor zu einem Punkt addieren, dann bekommt man einen neuen Punkt, aber 
man kann keine zwei Punkte addieren. Das ist ähnlich wie bei 
Speicheraddressen. Man kann zu einem Zeiger einen Offset addieren und 
bekommt einen neuen Zeiger. Man kann aber keine zwei Zeiger addieren.

von Daniel A. (daniel-a)


Lesenswert?

Das man Punkte nicht addieren kann mag ja Mathematisch gesehen stimmen, 
aber in einem Programm ist es einfach einfacher und Effizienter nicht 
extra zwischen Punkten und Vektoren zu unterscheiden. Schaut euch z.B. 
GLSL oder HLSL an, dort gibt es nur Vektoren und keine Punkte, und 
Punkte werden als Vektoren behandelt. Das ist die selbe Situation um 
180° gedreht. Punkt oder Vektor ist nur Gedanklich ein unterschied, im 
kartesisches Koordinatensystem haben beide die gleichen Komponenten.

von Mark B. (markbrandis)


Lesenswert?

Daniel A. schrieb:
> Punkt oder Vektor ist nur Gedanklich ein unterschied

Jein. Bei einem Punkt bezieht man sich ja immer auf den Ursprung und 
gibt die Koordinaten relativ zu diesem an. Ein Vektor kann einen 
beliebigen Anfangs- und Endpunkt haben.

Dem Punkt fehlt sozusagen ein Freiheitsgrad ;-)

von Template Neuling (Gast)


Lesenswert?

Hallo alle zusammen!

Ich konnte das jetzt mit folgendem Code lösen:
1
template<typename RHS>
2
    auto operator+(Point<RHS, dimensionsCount> const& rhs) -> Point< typename std::common_type<T, RHS>::type, dimensionsCount >
3
    {
4
        Point<typename std::common_type<T, RHS>::type, dimensionsCount> tmp;
5
6
        for (int i = 0; i < dimensionsCount; ++i) {
7
            tmp[i] = (*this)[i] + rhs[i];
8
        }
9
10
        return tmp;
11
    }

Im Grunde genommen ist das der Code, den  Torsten Robitzki am 24.12.2015 
08:59 vorgeschlagen hat, nur nicht als friend deklariert (sodass 'this' 
die linke Seite ist).
Eigentlich wäre mir die Version mit zwei Operanden lieber, weil ich dann 
eine Referenz anstatt des this Pointers verwenden könnte. Wenn ich der 
Funktion von Torsten Robitzki aber einfach ein friend davor packe, 
kompiliert der Code nicht, und zwar mit der Begründung: redifinition of 
'... operator+ ...' und zwar mit 'previously definded' in derselben 
Zeile. Wie kommt das?


Klaus W. schrieb:
> Nur mal am Rand: was soll eigentlich rauskommen, wenn man zwei Punkte
> addiert? Ein Doppelpunkt?

Wie schon andere hier sagten, es ist eine Vektor addition. Ich habe als 
Namen Point gewählt, weil mir Vector mit vector aus der STL zu leicht 
verwechselbar ist. Für eine sinnvollere Namensgebung bin ich offen!
Allerdings muss ich sagen, dass ich Point auch für Punkte in 
mehrdimensionalen Arrays verwende. Da kann man dann sinnvoll eine 
Elementenweise größer/kleiner Beziehung einführen, die in einem normalen 
Vektor der größer/kleiner Beziehung in der 1-Norm entsprechen würde. Da 
diese für Vektoren eher selten benutzt wird, finde ich Point gar nicht 
so verkehrt.

Hannes W. schrieb:
> Die Implementation von operator+ im
> ersten Post implementiert allerdings das Hadamard-Produkt. Wenn der TO
> den Code bei mir abliefern würde …

Das war ein copy paste Fehler, sorry.

Sebastian V. schrieb:
> Wobei man noch anmerken sollte, dass man mit common_type den Typ kriegt
> in den man beiden Typen konvertieren kann. Für eigene Typen ist das
> nicht zwingend der Typ der bei einer Addition/Multiplikation rauskommt.
> Für Builtin Types passt es aber.

Ich verwende in meiner Anwendung nur built-in types für Point. Die 
Anmerkung ist aber natürlich sehr gerechtfertigt! Danke dafür!

Mark B. schrieb:
> Daniel A. schrieb:
>> Punkt oder Vektor ist nur Gedanklich ein unterschied
>
> Jein. Bei einem Punkt bezieht man sich ja immer auf den Ursprung und
> gibt die Koordinaten relativ zu diesem an. Ein Vektor kann einen
> beliebigen Anfangs- und Endpunkt haben.
>
> Dem Punkt fehlt sozusagen ein Freiheitsgrad ;-)

Dem würde ich nicht zustimmen. Der Vektor hat diesen Freiheitsgrad eben 
nicht, denn egal wohin du ihn schiebst, anhand des Vektors selbst kannst 
du das nicht unterscheiden. Das is so, als würdest du beim Punkt den 
Nullpunkt zusammen mit dem Punkt woanders hinschieben.

Daniel A. schrieb:
> Um mal einige Usecases für Punktaditionen zu bringen:
>
> Mittelpunkt Berechnen

Das ist eine der Anwendungen, die ich dafür geplant habe =)

von Template Neuling (Gast)


Lesenswert?

Ich habe im letzten Post viel geschrieben, sodass das wichtigste wohl 
untergegangen ist:

Eine mögliche Lösung ist diese:
1
template<typename RHS>
2
    auto operator+(Point<RHS, dimensionsCount> const& rhs) -> Point< typename std::common_type<T, RHS>::type, dimensionsCount >
3
    {
4
        Point<typename std::common_type<T, RHS>::type, dimensionsCount> tmp;
5
6
        for (int i = 0; i < dimensionsCount; ++i) {
7
            tmp[i] = (*this)[i] + rhs[i];
8
        }
9
10
        return tmp;
11
    }

Aber eigentlich würde ich es lieber mit
1
friend auto operator+(lhs, rhs)
lösen, weil ich dann eine Referenz anstatt des this Pointers verwenden 
könnte (und weil es mich einfach interessiert).

Wenn ich der
Funktion von Torsten Robitzki aber einfach ein friend davor packe,
kompiliert der Code nicht, und zwar mit der Begründung: redifinition of
'... operator+ ...' und zwar mit 'previously definded' in derselben
Zeile. Hat jemand einen Tipp, wie das zustande kommt?

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


Lesenswert?

Template Neuling schrieb:

> Aber eigentlich würde ich es lieber mit
>
1
> friend auto operator+(lhs, rhs)
2
>
> lösen, weil ich dann eine Referenz anstatt des this Pointers verwenden
> könnte (und weil es mich einfach interessiert).

Wo siehst Du den einen this pointer in einer freien Funktion (operator+) 
und wo siehst Du hier überhaupt eine Anwendung für `friend` in Deinem 
code. Friend deklariert eine Funktion (nicht member), die auf die 
privaten member eines Objekts zugreifen darf. Wenn operator[] bei Deiner 
Klasse nicht privat ist, gibt es exakt gar keinen Grund, den operator+ 
zum friend zu deklarieren.

Am einfachsten wird es, wenn Du Dein Beispiel mal weiter eindampfst und 
kompilierbaren Code zeigst.

> Funktion von Torsten Robitzki aber einfach ein friend davor packe,
> kompiliert der Code nicht, und zwar mit der Begründung: redifinition of
> '... operator+ ...' und zwar mit 'previously definded' in derselben
> Zeile. Hat jemand einen Tipp, wie das zustande kommt?

das ist typischer Weise eine Fehlermeldung des linkers und bedeutet, 
dass der Linker die Definition einer Funktion in zwei 
Übersetzungseinheiten hat. Das passiert z.b., wenn man eine nicht 
statische Funktion in einem header definiert und den header an mehr als 
einer Stelle einbindet. Was das Problem bei Dir ist, kann keiner sagen, 
wenn Du nicht ein bisschen mehr von Deinem Code zeigst.

von Sebastian V. (sebi_s)


Lesenswert?

Der Fehler kommt wohl daher, dass beide Klassen Point<int> und 
Point<double> die gleiche Definition von operator+ bereit stellen. Damit 
gibt es zwei Definitionen. Eine Mögliche Lösung ist in der Klasse die 
Funktion nur als friend zu deklarieren und später zu definieren. Oder 
was auch zu funktionieren scheint ist wenn man nur einen Template 
Parameter nimmt und den anderen durch die Klasse vorgibt:
1
template < typename T >
2
struct Point {
3
  template < typename B >
4
  friend auto operator+(const Point<T>& a, const Point<B>& b) -> Point< decltype(T() * B()) >
5
  {
6
    auto val = a[0] * b[0];
7
    return Point< decltype(val) >();
8
  }
9
};

Edit: Natürlich gilt die Anmerkung von Torsten Robitzki, dass man sich 
überhaupt erstmal fragen sollte warum man friend benutzen möchte und 
dann noch die etwas wirre Konstruktion mit der Definition in der Klasse.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

In den meisten Fällen ist es einfacher, die eigentliche Addition in 
einen Operator += zu stecken und den Operator + dann nur noch darauf 
aufzusetzen.

von Template Neuling (Gast)


Lesenswert?

Hallo und ein frohes neues euch allen!


Torsten R. schrieb:
> Wo siehst Du den einen this pointer in einer freien Funktion (operator+)

Das war auf die Lösung vom 27.12.2015 21:32 bezogen. Wenn man den 
Operator mit nur einem Argument definiert, dann zeigt der this Pointer 
auf das linksseitige Element.

Torsten R. schrieb:
> Du hier überhaupt eine Anwendung für `friend` in Deinem
> code. Friend deklariert eine Funktion (nicht member), die auf die
> privaten member eines Objekts zugreifen darf. Wenn operator[] bei Deiner
> Klasse nicht privat ist, gibt es exakt gar keinen Grund, den operator+
> zum friend zu deklarieren.

Man kann den operator nicht mit zwei Variablen und als nicht friend 
deklarieren, sonst kommt der Fehler "...operator... must take either 
zero or one argument"


Sebastian V. schrieb:
> Der Fehler kommt wohl daher, dass beide Klassen Point<int> und
> Point<double> die gleiche Definition von operator+ bereit stellen.

Vielen Dank für diesen Tipp! Da wäre ich nicht ohne weiteres selbst 
drauf gekommen!

Sebastian V. schrieb:
> Eine Mögliche Lösung ist in der Klasse die
> Funktion nur als friend zu deklarieren und später zu definieren.

Soweit ich weiß, kann man template Klassen nicht in einer separaten .cpp 
definieren. Hierzu dieser Link: 
http://stackoverflow.com/questions/1724036/splitting-templated-c-classes-into-hpp-cpp-files-is-it-possible

Sebastian V. schrieb:
> Oder
> was auch zu funktionieren scheint ist wenn man nur einen Template
> Parameter nimmt und den anderen durch die Klasse vorgibt

Jop, das funktioniert und ist bei mir gar nicht so abwegig, da meine 
Klasse diesen template Parameter sowieso definiert!

Rolf M. schrieb:
> In den meisten Fällen ist es einfacher, die eigentliche Addition in
> einen Operator += zu stecken und den Operator + dann nur noch darauf
> aufzusetzen.

Das habe ich jetzt so umgeschrieben! Danke dafür! Trotzdem hätte das 
Problem weiterhin bestanden. Erst das Ersetzen des Template Parameters 
durch den der Klasse hat den Konflikt behoben.


Falls es jemand mal braucht, hier ist die wohl endgültige Lösung:
1
template<typename RHS>
2
    void operator+=(Point<RHS, dimensionsCount> const& rhs)
3
    {
4
        for (int i = 0; i < dimensionsCount; ++i) {
5
            elements[i] += rhs[i];
6
        }
7
    }
8
9
    template<typename RHS>
10
    friend auto operator+(Point<T, dimensionsCount> const&a,
11
            Point<RHS, dimensionsCount> const& b) -> Point< typename std::common_type<T, RHS>::type, dimensionsCount >
12
    {
13
        Point<typename std::common_type<T, RHS>::type, dimensionsCount> ret(a);
14
15
        ret += b;
16
        return ret;
17
    }

von Sebastian V. (sebi_s)


Lesenswert?

Template Neuling schrieb:
> Sebastian V. schrieb:
>> Eine Mögliche Lösung ist in der Klasse die
>> Funktion nur als friend zu deklarieren und später zu definieren.
>
> Soweit ich weiß, kann man template Klassen nicht in einer separaten .cpp
> definieren. Hierzu dieser Link:
> 
http://stackoverflow.com/questions/1724036/splitting-templated-c-classes-into-hpp-cpp-files-is-it-possible

Später im Sinne von nach der Definition der Klasse aber noch im Header.

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


Lesenswert?

Template Neuling schrieb:

> Torsten R. schrieb:
>> Du hier überhaupt eine Anwendung für `friend` in Deinem
>> code. Friend deklariert eine Funktion (nicht member), die auf die
>> privaten member eines Objekts zugreifen darf. Wenn operator[] bei Deiner
>> Klasse nicht privat ist, gibt es exakt gar keinen Grund, den operator+
>> zum friend zu deklarieren.
>
> Man kann den operator nicht mit zwei Variablen und als nicht friend
> deklarieren, sonst kommt der Fehler "...operator... must take either
> zero or one argument"

Och meno!!! Wenn ich schreibe, dass Du den Operator als _freie_ 
Funktion deklarieren sollst, dann sollst Du dass natürlich ausserhalb 
(z.B. nach) der Klassendefinition tun!.

Ein Operator der zwei Argumente vom selben Typen nimmt, ist meinst ganz 
gut als __freie__ Funktion definiert, da dies auch Konvertierungen 
(z.B. durch einen c'tor) auf der linken Seite des Operators erlaubt.


mfg Torsten

von Sebastian V. (sebi_s)


Lesenswert?

Durch die friend Definition in der Klasse wird die Funktion übrigens 
automatisch zu einer freien Funktion. Da Memberfunktionen sowieso 
Zugriff auf alle Member haben macht es natürlich keinen Sinn 
Memberfunktionen als friend zu haben.

von Nase (Gast)


Lesenswert?

Sebastian V. schrieb:
> Durch die friend Definition in der Klasse wird die Funktion übrigens
> automatisch zu einer freien Funktion. Da Memberfunktionen sowieso
> Zugriff auf alle Member haben macht es natürlich keinen Sinn
> Memberfunktionen als friend zu haben.
Doch, das kann sinnvoll sein. Eine freie Funktion hat im Umkehrschluss 
nämlich keinen Zugriff auf interne Member einer Klasse.

von Sebastian V. (sebi_s)


Lesenswert?

Hast du dir meinen Satz mit Verstand durchgelesen? Die Aussage war, dass 
es keinen Sinn macht Memberfunktionen der eigenen Klasse zu befreunden. 
Bei freien Funktionen oder Memberfunktionen anderer Klassen kann friend 
natürlich schon Sinn machen.

: Bearbeitet durch User
von Nase (Gast)


Lesenswert?

Sebastian V. schrieb:
> Hast du dir meinen Satz mit Verstand durchgelesen? Die Aussage war, dass
> es keinen Sinn macht Memberfunktionen der eigenen Klasse zu befreunden.
Doch, das ergibt durchaus Sinn, der hier und da einen Trick erlaubt.

Pratische Anwendung: https://woboq.com/blog/q_enum.html

Man mag jetzt natürlich über Sinn und Unsinn von Codegeneratoren und dem 
MOC-Kram philosophieren.

von Nase (Gast)


Lesenswert?

Nase schrieb:
> Sebastian V. schrieb:
>> Hast du dir meinen Satz mit Verstand durchgelesen? Die Aussage war, dass
>> es keinen Sinn macht Memberfunktionen der eigenen Klasse zu befreunden.
> Doch, das ergibt durchaus Sinn, der hier und da einen Trick erlaubt.

Klar, es ändert sich semantisch nichts.
Der Trick besteht halt darin, die friend-Funktion in die Klasse selbst 
zu inlinen, was ja per Makro machbar ist.

von Sebastian V. (sebi_s)


Lesenswert?

In deinem Beispiel wird doch auch davon geredet, dass die friend 
Funktion eben keine Memberfunktion ist. Gibt sogar ein Beispiel aus dem 
C++ Standard. §11.3 Abs. 6:
1
class M {
2
  friend void f() { } // definition of global f, a friend of M,
3
                      // not the definition of a member function
4
};
Hier ruft man die Funktion einfach durch f() auf. Nicht M::f() und auch 
nicht m.f() (mit m ein Objekt der Klasse M) oder irgendwas. Hier ist f 
eine freie Funktion!

von Nase (Gast)


Lesenswert?

Sebastian V. schrieb:
> In deinem Beispiel wird doch auch davon geredet, dass die friend
> Funktion eben keine Memberfunktion ist. Gibt sogar ein Beispiel aus dem
> C++ Standard. §11.3 Abs. 6:
> class M {
>   friend void f() { } // definition of global f, a friend of M,
>                       // not the definition of a member function
> };
> Hier ruft man die Funktion einfach durch f() auf.
Nein, dort ruft sie garnichts auf. Mehr noch, die Funktion f() _kann 
garnicht_ aufgerufen werden, denn sie ist nirgendwo sichtbar.
Probiers aus.
1
#include <iostream>
2
using namespace std;
3
4
class M {
5
  friend void f() {
6
    cout << "f()" << endl;
7
  }
8
};
9
10
int main() {
11
  f();
12
}
1
test.c++: In function ‘int main()’:
2
test.c++:12:4: error: ‘f’ was not declared in this scope
3
  f();
4
    ^

> Hier ist f eine freie Funktion!
Ja. Aber eine, die nicht aufgerufen werden kann, weil sie nirgendwo 
sichtbar ist. Sogar innerhalb der Klasse ist f() nicht sichtbar, denn 
friend ist keine Deklaration.

Der Trick ist, die "inline-friend"-Funktion per ADL an der 
Namensauflösung zu beteiligen. Das ist genau das, was bei Qt gemacht 
wird.

von Rolf M. (rmagnus)


Lesenswert?

Nase schrieb:
>> Hier ist f eine freie Funktion!
> Ja. Aber eine, die nicht aufgerufen werden kann, weil sie nirgendwo
> sichtbar ist. Sogar innerhalb der Klasse ist f() nicht sichtbar, denn
> friend ist keine Deklaration.

Wenn man sie außerhalb der Klasse deklariert, geht es aber. Ich wusste 
bisher gar nicht, dass es möglich ist, in C++ eine Funktion zu 
definieren, ohne sie dabei auch zu deklarieren. Mir wurde immer 
"eingetrichtert", dass jede Definition immer automatisch auch eine 
Deklaration ist.

: Bearbeitet durch User
von Sebastian V. (sebi_s)


Lesenswert?

Das man eine Deklaration außerhalb der Klasse braucht hat mich gestern 
auch verwirrt. Können wir uns jetzt wenigstens einigen, dass meine 
ursprüngliche Aussgabe stimmt? Nämlich, dass man Memberfunktionen der 
eigenen Klasse nicht als friend haben kann weil das erstens keinen Sinn 
macht und zweitens dafür scheinbar auch keine Syntax gibt.

von Nase (Gast)


Lesenswert?

Rolf M. schrieb:
> Ich wusste
> bisher gar nicht, dass es möglich ist, in C++ eine Funktion zu
> definieren, ohne sie dabei auch zu deklarieren.
Ich habe diese Sichtweise auch bis gestern nicht so richtig 
wahrgenommen...

Sebastian V. schrieb:
> Können wir uns jetzt wenigstens einigen, dass meine
> ursprüngliche Aussgabe stimmt? [...]
Ja, ich habe dir da ja im Prinzip auch nicht widersprochen.
Der einzige Knackpunkt war, dass ich die (innerhalb der Klasse) 
vereinbarte friend-Funktion als Member aufgefasst habe, weil sie halt in 
der Klasse vereinbart wurde.
Mit dieser neuen Sichtweise kann ich aber leben, ist irgendwie ja auch 
sinnvoll.

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.