Auswahl einer überladenen Methode



  • Hallo,

    ich verstehe hier nicht, warum der Compiler in diesem Beispiel immer die Variante #2 aufruft. Kann mir jemand sagen, warum?

    #include <iostream>
    
    using namespace std;
    
    template<typename T, unsigned int N>
    struct Base
    {
    	Base()
    	{
    	}
    
        // Variante 1
    	Base& operator+=( const Base& rhs )
    	{
    		cout << "Base& operator+=( const Base& rhs ) called" << endl;
    		return *this;
    	}
    
        // Variante 2
    	template<typename U>
    	Base& operator+=( const U& rhs )
    	{
    		cout << "Base& operator+=( const U& rhs ) called" << endl;
    		return *this;
    	}
    
        // Variante 3
    	template<typename U>
    	Base& operator+=( const Base<U,N>& rhs )
    	{
    		cout << "Base& operator+=( const Base<U,N>& rhs ) called" << endl;
    		return *this;
    	}
    };
    
    template<typename T>
    struct Derived : public Base<T,1>
    {
    	using Base<T,1>::operator+=;
    
    	Derived()
    	{
    	}
    };
    
    int main( int argc, char* argv[] )
    {
       Derived<int> d1;
       Derived<int> d2;
       Derived<double> d3;
    
       d1 += d2; // soll Variante #1 aufrufen, ruft aber #2 auf
       d1 += 2;  // soll Variante #2 aufrufen
       d1 += d3; // soll Variante #3 aufrufen, ruft aber #2 auf
    
       return 0;
    }
    

    Wie bringe ich den Compiler dazu, die entsprechende Variante aufzurufen?



  • Machst halt ne Vorwärtsdeklaration von Derived und noch einen Operator dazu.
    Google mal nach "c++ overload resolution" oder so. Da müsste schon was bei rauskommen.



  • Ansonsten hier noch ein Video zu Thema: https://www.youtube.com/watch?v=03KAWd6Yp8I



  • bist du sicher, dass du so einfach die operatoren aus der Basisklasse in derived verwenden willst? das sieht mir ziemlich gefährlich und nach slicing aus...

    ansonsten kannst du dir ja die eine funktion ja so hinbiegen, dass sie deinen eigenen "regeln" entspricht:

    #include <type_traits>
    
    //Variante 2
    template<typename U> 
    std::enable_if_t<
      !std::is_base_of<Base, U>::value
      && std::is_same<T, U>::value, 
      Base&>
        operator+=( const U& rhs ) 
        { 
            cout << "Base& operator+=( const U& rhs ) called" << endl; 
            return *this; 
        }
    


  • Vielleicht funktioniert das Ganze doch nicht so, wie ich mir das vorstelle. Im Prinzip habe ich folgendes vor: Ich möchte mehrdimensionale Arrays als Klassen kapseln, dazu gibt es eine Klasse ArrayBase .

    #include <array>
    #include <vector>
    
    template<typename T,unsigned int N>
    class ArrayBase
    {
       std::vector<T>              Elements_; // Elemente des Arrays
       std::array<std::size_t,N>   Extents_;  // Größe der einzelnen Dimensionen
       std::array<std::size_t,N>   Strides_;  // Offset für nächstes Element in der gleichen Dimension
    
    public:
       ArrayBase()
       {
       }
    
       virtual ~ArrayBase()
       {
       }
    
       ... div. Methoden wie size(), empty(), clear(), etc. Was man halt von der
       STL so kennt.
    };
    

    Davon leitet die Spezialisierung für ein 2-dimensionales Array ab, die Methoden mitbringt, die nur für 2D Arrays vorgesehen sind:

    template<typename T>
    class Array2D : public ArrayBase<T,2>
    {
    public:
       Array2D()
       {
       }
    
       // Größe des Arrays setzen
       void resize( std::size_t d0, std::size_t d1 )
       {
          ...
       }
    
       T& operator()( std::size_t d0, std::size_t d1 )
       {
          ...
       }
    
       const T& operator()( std::size_t d0, std::size_t d1 ) const
       {
          ...
       }
    };
    

    Nun möchte ich Rechenoperationen auf dem Array zulassen, z.B. die Addition eines anderen Arrays oder eines einzelnen Wertes. Effektiv ist das einfach nur das Addieren der einzelnen Arrayelemente/Werte, unabhänigig von der Anzahl der Dimensionen. Also sollte das in der Basisklasse implementiert werden können. Ich brauche dazu für jede Addition drei Überladungen, eine für Skalare und zwei für Arrays (gleicher Datentyp/anderer, konvertierbarer Datentyp).

    template<typename T,unsigned int N>
    class ArrayBase
    {
       ...
    
    public:
       ArrayBase& operator+=( const ArrayBase& rhs )
       {
          // Arrayelemente aufaddieren
          return *this;
       }
    
       template<typename U>
       ArrayBase& operator+=( const ArrayBase<U,N>& rhs )
       {
          // Arrayelemente aufaddieren
          return *this;
       }
    
       template<typename U>
       ArrayBase& operator+=( const U& rhs )
       {
          // Wert zu Arrayelementen addieren
          return *this;
       }
    };
    
    // freie Funktionen
    template<typename T,unsigned int N>
    ArrayBase<T,N> operator+( const ArrayBase<T,N>& lhs, const ArrayBase<T,N>& rhs )
    {
       ArrayBase<T,N> tmp( lhs );
       return tmp += rhs;
    }
    
    template<typename T,typename U,unsigned int N>
    ArrayBase<T,N> operator+( const ArrayBase<T,N>& lhs, const ArrayBase<U,N>& rhs )
    {
       ArrayBase<T,N> tmp( lhs );
       return tmp += rhs;
    }
    
    template<typename T,typename U,unsigned int N>
    ArrayBase<T,N> operator+( const ArrayBase<T,N>& lhs, const U& rhs )
    {
       ArrayBase<T,N> tmp( lhs );
       return tmp += rhs;
    }
    
    template<typename T,typename U,unsigned int N>
    ArrayBase<T,N> operator+( const U& lhs, const ArrayBase<T,N>& rhs )
    {
       return rhs + lhs;
    }
    

    Diese ganzen Operationen möchte ich jetzt nicht für alle Array Varianten wiederholen. Vielleicht ist der Ansatz auch Murks, oder ich muss wirklich alles für jede Variante wiederholen 😞



  • zum design: ich würde vorsichtiger sein. was du vorhast, sieht nicht (unbedingt) nach vererbung aus.

    vielleicht so:

    template <class T, int N>
    class ArrayImpl { 
       //hier alle funktionen, die allgemein für arrays interessant sind
    };
    
    template <class T, class Impl>
    class ArrayBase : private Impl { 
    //so kommst du nicht so leicht in die verlegenheit, hier sachen zu nutzen, die von einem bestimmten N abhängen
    
    protected:
      ArrayImpl& get_impl() { return *this; }
      //hier tatsächlich nur funktionen, die mit für alle arrays erlaubt sind
    };
    
    template <class T>
    class Array2D : public ArrayBase<T, ArrayImpl<T,2>> {
    
    }
    

    ansonsten zur überladung willst du wahrscheinlich genau so etwas:

    template <class U>
    enable_if_t<is_scalar<U>::value> operator+=(U const& something)
    


  • Ich denke schon, dass Vererbung hier richtig ist. Ein n-dimensionales Array ist ein generisches Array. In den abgeleiteten Klasse müssten eigentlich nur 5 Methoden ergänzt werden:

    • resize(...) mit der entsprechenden Anzahl der Parameter
    • operator()(...) mit der entsprechenden Anzahl der Parameter (const + nonconst)
    • at(...) mit der entsprechenden Anzahl der Parameter (const + nonconst)

    Nur muss ich jetzt wohl für jede Ableitung die mathematischen Operationen anbieten. Irgendwie unbefriedigend.

    Vielleicht kann man mit eigenen Type Traits noch was basteln...



  • hm, alle drei methoden könntest du auch mit variadic templates für jede multi-array-klasse anbieten.

    template <typename... D>
        auto resize (D ... d) -> 
            enable_if_t<
                        sizeof...(D)==Dim
                        //C++17: && (is_convertible<D, size_t>::value && ...)> 
                       >
    
        { 
            cout << "MultiArray<T,Dim>::resize\n";
        }
    

    ein zweidimensionales array ist nur deshalb ein generisches array, weil die dimension des generischen arrays auf zwei fixiert ist.
    was du eigentlich willst, ist eine (template-) spezialisierung des templates für Dim=2 ohne dinge doppelt zu implementieren, und mit der richtigen automatischen namensauflösung. dafür könntest du diese klassenhierarchie verwenden:

    //gemeinsame funktionen, eher intern
    template <class T, int Dim>
    class MultiArrayBase {
    protected:
       vector<T> elements;
       array<size_t, Dim> extends;
    };
    
    //generisch
    template <class T, int Dim>
    class MultiArrayImpl : public MultiArrayBase<T, Dim> {
    public:
        template <typename... D>
        auto resize (D ... d) -> 
            enable_if_t<
                        sizeof...(D)==Dim
                       >
    
        { 
            cout << "MultiArray<T,Dim>::resize\n";
        }
    };
    
    //speziell
    template <class T>
    class MultiArrayImpl<T, 2> : public MultiArrayBase<T, 2> {
    public:
       auto resize (size_t d0, size_t d1)
       {
            cout << "MultiArray<T, 2>::resize\n";
       }
    };
    
    //öffentliches interface
    template <class T, int Dim>
    class MultiArray final : public MultiArrayImpl<T, Dim> {
    public:
        MultiArray& operator+= (MultiArray const& other) {
            cout << "+= other\n";
            return *this;
        }
    
        template <class U>
        MultiArray& operator+= (U const& something) {
            cout << "+= something\n";
            return *this;
        }
    
        template <class U>
        MultiArray& operator+= (MultiArray<U, Dim> const& other) {
            cout << "+= other-convert\n";
            return *this;
        }
    };
    
    //extra-name
    template <class T>
    using MultiArray2D = MultiArray<T, 2>;
    
    int main () {
        MultiArray<int, 4> arr;
        arr.resize(1, 2, 3, 4);
    
        MultiArray<int, 2> d1;
        MultiArray<int, 2> d2;
        MultiArray<double, 2> d3;
    
        d1.resize(1, 2);
    
        d1 += d2;
        d1 += 2;
        d1 += d3;
    }
    


  • Hui, danke. Das muss ich mir mal in Ruhe zu Gemüte führen.

    Nicht schlecht, die Spezialisierung zwischenzuschieben. Nu hab ich´s auch verstanden.


Anmelden zum Antworten