array übergeben



  • 😉 ok danke für Eure Beiträge.



  • so leider noch eine Frage.

    Wenn man an eine funktion ein mehrdimensionales array übergibt muss die größe
    der 2.ten - nten dimension feststehen.

    z.B.

    funktion(arr[][4],int arraysize)
    

    Muss ich jetzt für jedes array einer anderen Größe eine neue Funktion schreiben ?


  • Mod

    blurry333 schrieb:

    Muss ich jetzt für jedes array einer anderen Größe eine neue Funktion schreiben ?

    Wow, eine ausformulierte Frage. 👍 . Da antworte ich sogar mal:

    Nein. Übergib dir die Größen und einen Pointer auf das erste Element und rechne in der Funktion die Indizes selber um (z.B. 2D: Index [x][y] entspricht (Anfang + x * y-Größe + y) . Oder lass mehrdimensionale Arrays gleich bleiben, es gibt in C++ keinen Grund, sich so zu quälen.



  • In C wäre es üblich, die Indizierung zur Laufzeit von Hand zu machen, d.h.

    void foo(int *array, size_t dim_y, size_t dim_x) {
      size_t i, j;
    
      array[i * dim_y + j]; // entspricht array[i][j].
    }
    

    Aber wir sind hier ja zum Glück im C++-Forum, wo man Operatoren überladen und TMP betreiben kann:

    #include <cstddef>
    #include <iostream>
    
    template<typename T>
    class array2d {
    public:
      template<std::size_t M, std::size_t N>
      inline array2d(T (&data)[M][N])
      : data_(static_cast<T*>(static_cast<void*>(data))), dim_x_(N), dim_y_(M) { }
    
      inline T       *operator[](std::size_t y)       { return data_ + dim_x_ * y; }
      inline T const *operator[](std::size_t y) const { return data_ + dim_x_ * y; }
    
      inline std::size_t dim_x() const { return dim_x_; }
      inline std::size_t dim_y() const { return dim_y_; }
    
    private:
      T *data_;
      std::size_t dim_x_;
      std::size_t dim_y_;
    };
    
    void funktion(array2d<int> const &arr) {
      for(std::size_t i = 0; i < arr.dim_y(); ++i) {
        for(std::size_t j = 0; j < arr.dim_x(); ++j) {
          std::cout << arr[i][j] << ' ';
        }
        std::cout << '\n';
      }
    }
    
    int main() {
      int a[][3] = { { 1, 2, 3 },
                     { 4, 5, 6 } };
    
      funktion(a);
    }
    

    Das macht im Prinzip das selbe, nur halt schick verpackt.



  • Memberfunktionen sind implizit inline 🤡



  • seldon schrieb:

    In C wäre es üblich, die Indizierung zur Laufzeit von Hand zu machen, d.h.

    void foo(int *array, size_t dim_y, size_t dim_x) {
      size_t i, j;
    
      array[i * dim_y + j]; // entspricht array[i][j].
    }
    
    // Entspricht undefiniertem Verhalten.
    


  • 314159265358979 schrieb:

    Memberfunktionen sind implizit inline 🤡

    Ihm ging es eh nur darum den Code aufzublaehen und bissi zum zu posen.



  • Ich frage mich ja, ob nicht das hier UB ist:

    static_cast<T*>(static_cast<void*>(data))
    


  • das dim_y in

    array[i * dim_y + j];
    

    muss natürlich dim_x heißen, mein Fehler.

    Was

    static_cast<T*>(static_cast<void*>(data))
    

    angeht, das ist sehr genau definiert. Der Umweg über void* ist aufgrund des strengen Typsystems in C++ notwendig. In der Praxis dürfte

    reinterpret_cast<T*>(data)
    

    wohl das selbe machen, aber die Definition von reinterpret_cast ist deutlich weniger streng.



  • seldon schrieb:

    das dim_y in

    array[i * dim_y + j];
    

    muss natürlich dim_x heißen, mein Fehler.

    Asoooo, dann:

    // seldons Code
    void foo(int *array, size_t dim_y, size_t dim_x) {
      size_t i, j; // enspricht undefiniertem Verhalten.
    
      array[i * dim_x + j]; // entspricht array[i][j].
    }
    


  • Dann füg noch /* i, j initialisieren */ ein. Meine Güte, ich wollte damit ein Prinzip verdeutlichen, nicht einsatzbereiten Code zur Verfügung stellen.



  • ubler schrieb:

    seldon schrieb:

    das dim_y in

    array[i * dim_y + j];
    

    muss natürlich dim_x heißen, mein Fehler.

    Asoooo, dann:

    // seldons Code
    void foo(int *array, size_t dim_y, size_t dim_x) {
      size_t i, j; // enspricht undefiniertem Verhalten.
    
      array[i * dim_x + j]; // entspricht array[i][j].
    }
    

    Bist du so blöd oder stellst du dich nur so?



  • seldon schrieb:

    : data_(static_cast<T*>(static_cast<void*>(data))), dim_x_(N), dim_y_(M) { }
    

    Das geht auch einfacher und auch so, dass T = const Irgendwas unterstützt wird:

    : data_(data[0]), ...
    

    seldon schrieb:

    inline T       *operator[](std::size_t y)       { return data_ + dim_x_ * y; }
      inline T const *operator[](std::size_t y) const { return data_ + dim_x_ * y; }
    

    Die Klasse hat Referenzsemantik. Da ist diese const-Überladung unangebracht. Der Zugriff auf das Array verändert die "Referenz" ja nicht. Das inline ist auch überflüssig. Von daher würde ich schreiben:

    T *operator[](std::size_t y) const { return data_ + dim_x_ * y; }
    

    Den Konstruktor kann man auch noch etwas flexibler machen:

    template<class U, std::size_t M, std::size_t N>
      array2d(U (&data)[M][N], typename boost::enable_if_c<
        boost::is_same<T,U>::value || boost::is_same<T,const U>::value
      >::type* =0)
      : data_(data[0]), dim_x_(N), dim_y_(M) {}
    

    Dann klappt das auch mit dem const richtig:

    void funktion(array2d<[b]const[/b] int> arrayref) {
      for(std::size_t i = 0; i < arrayref.dim_y(); ++i) {
        for(std::size_t j = 0; j < arrayref.dim_x(); ++j) {
          std::cout << arrayref[i][j] << ' ';
        }
        std::cout << '\n';
      }
    }
    

    Jetzt fehlt eigentlich nur noch eine implizite Konvertierung array2d<Dings> nach array2d<const Dings> in der Klasse. Man darf natürlich keine Derived->Base Konvertierung zulassen. Deswegen habe ich oben auch nicht convertible<U*,T*>::value verwendet.

    mein Senf,
    kk



  • Ethon schrieb:

    #include <iostream>
    #include <array>
    
    template<std::size_t Size1, std::size_t Size2>
    void funk(std::array<std::array<int, Size1>, Size2>& p)
    {
      cout<<"Funktioniert";
    }
    
    int main()
    {
      std::array<std::array<int, 4>, 3> arr;
      funk(arr);                                       
    }
    

    Fixed

    Was spricht gegen diese Lösung von mir?
    Reines Interesse, da eure Lösungen immer komplizierter werden.


  • Mod

    Irgendwie gefallen die mir alle nicht, da es Compilezeitlösungen sind. Wenn ich die Ausdehnung schon zur Compilezeit kenne, dann kann ich mir auch eine passende Funktion schreiben.



  • @SeppJ
    Der "array2d" Variante von seldon kann man ja leicht nen nicht-Template Konstruktor verpassen, der die Dimensionen "runtime" entgegennimmt.

    Ich würde das Ding allerdings nicht "array2d" nennen, sondern vielleicht "array_view_2d" oder so. Und der Klasse ein "stride" Member verpassen, so dass man aus einer "array_view_2d" ein "Stück ausschneiden" kann, und wieder eine "array_view_2d" erhält, ohne irgendwas kopieren zu müssen.



  • krümelkacker spricht eine Reihe guter Punkte an. Die implizite Konvertierung array2d<T> -> array2d<T const> ist über einen entsprechenden Konstruktor einfach zu haben:

    array2d(array2d<typename boost::remove_const<T>::type> const &other)
        : data_ (other[0]),
          dim_x_(other.dim_x()),
          dim_y_(other.dim_y()) { }
    

    Unschön ist, dass volatile und const volatile sich dann nicht auf die gleiche Weise entfernen lassen (man würde den gleichen Konstruktor mehrfach definieren, wenn T nicht const volatile ist). Das lässt sich bestimmt auch irgendwie über type traits lösen, aber damit befasse ich mich morgen.


  • Mod

    seldon schrieb:

    Unschön ist, dass volatile und const volatile sich dann nicht auf die gleiche Weise entfernen lassen

    Ich kann keinen Sinn in so einer Konvertierung erkennen.



  • Das war vielleicht etwas missverständlich ausgedrückt. Ich meine "entfernen lassen vom Eingabetyp", d.h. implizit ranbappen. Es geht um array2d<int> -> array2d<int volatile>.

    Jetzt braucht man volatile nicht besonders häufig, aber wenn ich eine Funktion habe, die ein zweidimensionales Integer-Array entgegennehmen können soll, auch wenn es volatile ist, bräuchte ich diese Umwandlung.

    Im Grunde hat krümelkacker die Lösung aber vorgelegt. So sollte es gehen:

    #include <cstddef>
    #include <iostream>
    
    #include <boost/utility/enable_if.hpp>
    #include <boost/type_traits.hpp>
    
    template<typename T>
    class array2d {
    public:
      template<typename U> struct is_compatible_type {
        static bool const value =
          boost::is_same<typename boost::remove_cv      <T>::type, U>::value ||
          boost::is_same<typename boost::remove_const   <T>::type, U>::value ||
          boost::is_same<typename boost::remove_volatile<T>::type, U>::value;
      };
    
      template<typename U, std::size_t M, std::size_t N>
      inline array2d(U (&data)[M][N], typename boost::enable_if_c<is_compatible_type<U>::value>::type * = 0)
      : data_ (data[0]),
        dim_x_(N),
        dim_y_(M) { }
    
      template<typename U>
      inline array2d(array2d<U> const &other, typename boost::enable_if_c<is_compatible_type<U>::value>::type * = 0)
        : data_ (other[0]),
          dim_x_(other.dim_x()),
          dim_y_(other.dim_y()) { }
    
      // Für SeppJ
      template<typename U>
      inline array2d(U *data, std::size_t dim_y, std::size_t dim_x, typename boost::enable_if_c<is_compatible_type<U>::value>::type * = 0)
        : data_ (data),
          dim_x_(dim_x),
          dim_y_(dim_y) { }
    
      inline T *operator[](std::size_t y) const { return data_ + dim_x_ * y; }
    
      inline std::size_t dim_x() const { return dim_x_; }
      inline std::size_t dim_y() const { return dim_y_; }
    
    private:
      T *data_;
      std::size_t dim_x_;
      std::size_t dim_y_;
    };
    

  • Mod

    seldon schrieb:

    Das war vielleicht etwas missverständlich ausgedrückt. Ich meine "entfernen lassen vom Eingabetyp", d.h. implizit ranbappen. Es geht um array2d<int> -> array2d<int volatile>.

    Das war schon klar.

    seldon schrieb:

    Jetzt braucht man volatile nicht besonders häufig, aber wenn ich eine Funktion habe, die ein zweidimensionales Integer-Array entgegennehmen können soll, auch wenn es volatile ist, bräuchte ich diese Umwandlung.

    Und genau das macht keinen Sinn. Im Gegensatz zu const führt volatile nicht zu irgenwelchen Einschränkungen bei den Algorithmen, die solche Daten benutzen (im Gegenteil). Jeder Algorithmus, der mit nicht-volatile Daten arbeitet, kann auch mit volatile Daten arbeiten. Wird der Algorithmus aber nun für volatile Daten geschrieben und mit nicht-volatile Daten gefüttert, würde das sinnloserweise zu einer erheblichen Verschlechterung der Performance führen.


Anmelden zum Antworten