Visual C++: Operatorüberladung mit Namensräumen

Schnelle objektorientierte, kompilierende Programmiersprache.
Antworten
Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Visual C++: Operatorüberladung mit Namensräumen

Beitrag von Xin » So Okt 18, 2009 10:58 am

Zum ersten muss ich sagen, dass sich der Visual C++ Compiler semantisch eigentlich mehr oder mehr minder korrekt verhält, aber das hilft mir nicht, wenn die Fehlermeldung mich darauf nicht aufmerksam macht. Insbesondere ist es nicht hilfreich, wie sich VC++ hier verhält.

Folgender Code, ich suche ein Element in einer Liste, das einen bestimmten Namen trägt:

Code: Alles auswählen

    T * Find( String const & identifier ) const
    {
      if( identifier.IsNotNull() )
        for( T * temp = static_cast<T *>( List<T>::First ); temp; temp = (T*) temp->BaseNode<T>::Succ )
          if( identifier == temp->GetIdentifier() ) )
            return temp;

      return NULL;
  }
Es gibt eine globale Überladung, um Strings zu vergleichen:

Code: Alles auswählen

inline bool operator == ( XSD::String::String const & lhs, XSD::String::String const & rhs )
Die Überladung ist durchgehend ohne Probleme im Einsatz.
Trotzdem ist die Bedingung if( identifier == temp->NodeIdentifier::GetIdentifier() ) ) immer wahr, ich finde also immer das erste Element oder keins.
Um sicher zu stellen, dass hier wirklich Strings verglichen werden, schließlich gibt es einen eindeutigen Operator dafür, sorge ich dafür, dass es keine falsche Überladung von GetIdentifier() geben darf:

Code: Alles auswählen

          if( identifier == temp->NodeIdentifier::GetIdentifier() ) )
Gleiches Ergebnis, auch die Langform hilft nicht weiter, obwohl hier defintiv und ohne Zweifel zwei Strings verglichen werden :

Code: Alles auswählen

    T * Find( String const & identifier ) const
    {
      if( identifier.IsNotNull() )
        for( T * temp = static_cast<T *>( List<T>::First ); temp; temp = (T*) temp->BaseNode<T>::Succ )
        {
          String const & rhs = temp->NodeIdentifier::GetIdentifier();

          if( identifier == rhs ) )
            return temp;
        }

      return NULL;
  }
Ich finde heraus, dass Visual C++ die Strings als Bools vergleicht, heißt: zuerst werden beide Strings implizit in Bools konvertiert, die - da sie beide Text enthalten, beide wahr sind. wahr == wahr, also ist das ganze immer wahr.
Ich nehme also die den operator bool() für die Strings wieder raus und erhalte folgende Fehlermeldung:

Code: Alles auswählen

c:\users\xin\desktop\genesys\trunk\de\xsd\util\list.h(548) : error C2665: "XSD::Util::operator ==": Durch keine der 2 Überladungen konnten alle Argumenttypen konvertiert werden.
        c:\users\xin\desktop\genesys\trunk\de\xsd\util\tagged.h(13): kann 'bool XSD::Util::operator ==(XSD::Util::TagType,XSD::Util::TagType)' sein
        c:\users\xin\desktop\genesys\trunk\de\xsd\util\tagged.h(55): oder "bool XSD::Util::operator ==(XSD::Util::TagSave,XSD::Util::TagSave)"
        bei Anpassung der Argumentliste '(const XSD::String::String, const XSD::String::String)'
        c:\users\xin\desktop\genesys\trunk\de\xsd\util\list.h(542): Bei der Kompilierung der  Klassen-template der XSD::Util::XMLBaseNode *XSD::Util::NamedList<T>::Find(const XSD::String::String &) const-Memberfunktion
        with
        [
            T=XSD::Util::XMLBaseNode
        ]
        c:\users\xin\desktop\genesys\trunk\de\xsd\util\xml.h(125): Siehe Verweis auf die Instanziierung der gerade kompilierten Klassen-template "XSD::Util::NamedList<T>".
        with
        [
            T=XSD::Util::XMLBaseNode
        ]
Mein operator == für Strings steht überhaupt nicht zu Diskussion, wird aber definitiv über ein Include eingebunden.

Visual C++ blendet Operatoren in höher gelegenen Namensräumen aus. Also findet er keinen und beginnt implizit und ohne Warning zum Erstbesten zu konvertieren, was ihm einfällt: bool, obwohl ein Operator existiert, dessen Signatur 100%ig passt - nur halt nicht im aktuellen Namensraum.
Ausgeblendet wurden die globalen 'operator ==', weil ich versehentlich die Operatoren für TagType und TagSave nicht global gesetzt habe.
Da man bei einen Operator in Kurzschreibweise selbst nicht wählen kann, aus welchem Namensraum er kommen soll ('if( identifier ::== rhs )'), muss man die lange Schreibweise wählen:

Code: Alles auswählen

    T * Find( String const & identifier ) const
    {
      if( identifier.IsNotNull() )
        for( T * temp = static_cast<T *>( List<T>::First ); temp; temp = (T*) temp->BaseNode<T>::Succ )
        {
          String const & rhs = temp->NodeIdentifier::GetIdentifier();

          if( ::operator ==( identifier, rhs ) )
            return temp;
        }

      return NULL;
    }
Das lässt sich nun wieder kompilieren und funktioniert auch unter Visual C++.

Um den Operator == in den jeweiligen Namensraum zu holen, wäre ein using operator ==; erforderlich gewesen. Sogesehen macht der Visual C++ hier alles richtig. Hilfreich ist es aber dennoch nicht, zumindest eine Warning 'Ich weiß, was Du diesen Sommer nicht tun wolltest' wäre nicht ganz schlecht gewesen. Auch die kommentarlose, implizite Konvertierung von gleich zwei Parametern halte ich für leicht fragwürdig.

Die letzte Version funktioniert jetzt, wie gewünscht. Ich baue jetzt die beiden versehentlich nicht globalen operatoren um, so dass sie global und damit gleichberechtigt stehen.
Und die Moral aus der G'schicht: operatoren in Namensräumen nutzt man nicht... (das steht glaube ich auch bei Scott Meyers, aber war ja auch hier nicht beabsichtigt ^^)
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: Visual C++: Operatorüberladung mit Namensräumen

Beitrag von Xin » So Okt 18, 2009 11:38 am

Gar nicht so spaßig, Visual C++ den Spaß beizubiegen. Namespaces sind ja eine gute Erfindung, aber irgendwie ist die Unterstützung ebendieser etwas zu kurz gekommen. :-/

Code: Alles auswählen

namespace XSD {
namespace Util {
  class TagType;
}}

bool operator == ( XSD::Util::TagType lhs, XSD::Util::TagType rhs );
bool operator != ( XSD::Util::TagType lhs, XSD::Util::TagType rhs );

namespace XSD {
namespace Util {

class TagType
{
  friend bool ::operator ==( ::XSD::Util::TagType lhs, ::XSD::Util::TagType rhs);
  friend bool ::operator !=( ::XSD::Util::TagType lhs, ::XSD::Util::TagType rhs);

  ...
}

}}

inline bool operator == ( XSD::Util::TagType lhs, XSD::Util::TagType rhs ) { ... }
inline bool operator != ( XSD::Util::TagType lhs, XSD::Util::TagType rhs )  { ... }
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
Kerli
Beiträge: 1456
Registriert: So Jul 06, 2008 10:17 am
Wohnort: Österreich
Kontaktdaten:

Re: Visual C++: Operatorüberladung mit Namensräumen

Beitrag von Kerli » So Okt 18, 2009 4:15 pm

Xin hat geschrieben:Ich finde heraus, dass Visual C++ die Strings als Bools vergleicht,
Für solche Fälle entstand das Safe Bool Idiom [1][2]. In C++0x wird das aber Gott sei dank nicht mehr notwendig sein, da dann das Schlüsselwort 'explicit' auch auf Castoperatoren ausgeweitet wird.

[1] http://en.wikibooks.org/wiki/More_C%2B% ... /Safe_bool
[2] http://www.artima.com/cppsource/safebool.html
"Make it idiot-proof and someone will invent an even better idiot." (programmers wisdom)

OpenGL Tutorials und vieles mehr rund ums Programmieren: http://www.tomprogs.at

Antworten