array übergeben
-
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.
-
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.
-
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_; };
-
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.
-
camper schrieb:
Jeder Algorithmus, der mit nicht-volatile Daten arbeitet, kann auch mit volatile Daten arbeiten.
Gerade das ist nicht der Fall. Code, der mit nicht-volatilen Daten arbeitet, kann eine Reihe Abkürzungen nehmen (bzw. der Compiler kann diese einbauen), die mit volatilen Daten nicht funktionieren. Umgekehrt beeinflusst die Abwesenheit solcher Abkürzungen die Korrektheit des Algorithmus nicht.
Es ist schon richtig, dass dabei Performance verlorengehen kann, aber wenn sich das als kritisch herausstellt, kann man immer noch zwei verschiedene Funktionen zur Verfügung stellen. So oder so macht es nur Sinn, array2d<foo volatile> in Zusammenhängen zu benutzen, wo man mit volatilen Daten rechnen muss, und da kann man dann nicht einfach darauf verzichten.