Seite 1 von 2
partielle Template Spezialisierung multi-dimension C-arrays
Verfasst: 30.11.2010, 18:30
von kaiserludi
Moin. Ich habe eine Templateklasse, die als Datencontainer für bestimmte Typen gilt. Für die verschiedenen unterstützten Datentypen gibt es Spezialisierungen des Templates für bestimmte Methoden, für die es bewusst keine Generalisierte Version gibt.
Foo<int>(aInt) funzt also, Foo<unsupportedType>(unsupportedType), Foo<unsupportedType>(int), Foo<int>(unsupportedType) hingegen werfen alle Compilerfehler.
Dies garantiert Typsicherheit in der API unserer Library. Was unterliegende Schichten nicht verarbeiten können, lässt die API gar nicht erst durch, weil die Container, die sie erwartet, es nicht aufnehmen können.
Nun wollen wir Support für multidimensionale C-Arrays adden und wenn ich die in Form von void* in der API erwarte, dann war es das natürlich, mit der typsicheren API.
Eigene Spezialisierungen oder Überladungen für alle Dimensionen sind natürlich nicht möglich, denn es können es können ja theoretisch n-dimensionale Arrays mit n gegen unendlich übergeben werden (in der Praxis setzt natürlich der Speicherbereich der Variable, die die Anzahl der Dimensionen speichert, eine natürliche Grenze).
Es gibt ja nun partielle Spezialisierungen für Templates, die klassischerweise für Pointer verwendet werden, also <Etype> als generelle Implementation des Templates, <Etype*> als Spezialisierung für alle Arten von eindimensionalen Pointern auf egal welchen Typ. Was mir nun vorschwebt, wäre eine partielle Spezialisierung für C-Arrays jeglicher Dimension eines bestimmten Types.
int*, int**, int***, int****, usw. würden also alle akzeptiert werden, unsupportedType* hingegen nicht.
Ist sowas möglich? Wenn ja, wie sähe die Syntax aus?
Habe dazu leider weder im Web noch im Stroustrup was gefunden.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 30.11.2010, 18:42
von Krishty
Rekursion, wie immer bei Templates?
Code: Alles auswählen
template <typename CType> struct TCContainerTypeFor { typedef Foo<CType> type; };
template <typename CElementType> struct TCContainerTypeFor<CElementType *> { typedef Foo<TCContainerTypeFor<CElementType>::type> type; };
static_assert(typeid(TCContainerTypeFor<int>::type) == typeid(Foo<int>), "!");
static_assert(typeid(TCContainerTypeFor<int**>::type) == typeid(Foo<Foo<Foo<int>>>), "!!");
Nur hingehackt – nur, falls ich dich richtig verstanden habe – ohne Gewähr. Musst du natürlich noch für die
const/
volatile-Version überladen und – falls du statische Arrays benutzt – am besten noch eine Version hinzufügen, die die Bereichsgrenzen prüft.
Gruß, Ky
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 30.11.2010, 20:25
von kaiserludi
Hmm, ich bin mir nicht ganz sicher, ob du mich nicht verstanden hast oder ich dich nicht, habe mit Templates auch noch nicht so viel Erfahrung, als dass ich mich da als absolut sattelfest sehen würde.
Ich habe folgende Klasse:
Code: Alles auswählen
template <class Etype>
class ValueObject:public Object
{
public:
/* Summary
Destructor. */
~ValueObject(void);
ValueObject(const ValueObject<Etype>& toCopy);
ValueObject(const Object* const obj);
ValueObject(const nByte data);
ValueObject(const short data);
ValueObject(const int data);
ValueObject(const int64 data);
ValueObject(const bool data);
ValueObject(const nByte* data, const int size);
ValueObject(const int* data, const int size);
ValueObject(const JString& data);
ValueObject(const JString* data, const int size);
ValueObject(const JVector<Object>& data);
ValueObject(const Hashtable& data);
ValueObject(const float data);
ValueObject(const double data);
ValueObject(const int64* data, const int size);
ValueObject(const bool* data, const int size);
ValueObject(const float* data, const int size);
ValueObject(const short* data, const int size);
ValueObject(const double* data, const int size);
Etype getDataCopy(void);
Etype* getDataAddress(void);
private:
void convert(const Object* const obj, nByte type);
ValueObject<Etype>& operator=(const ValueObject<Etype>& notToUse);
};
Nun hätte ich auch gerne die Möglickeit, neben Aufrufen wie ValueObject<int>(myInt) oder ValueObject<int*>(myIntArray, size) auch solche Calls zu ermöglichen:
ValueObject<int******>(aSixDimensionalIntArray, sizes), wobei sizes ein eindimensionaler array wäre mit so vielen Elementen, wie Parameter 1 an Dimensionen hat.
Calls wie ValueObject<long double**>(aLongDouble) sollen aber weiterhin nicht erlaubt sein.
Natürlich könnte ich das erreichen, in dem ich einfach folgende Überladungen zur Klasse hinzufüge:
ValueObject(const int* data, const int* sizes);
ValueObject(const int** data, const int* sizes);
ValueObject(const int*** data, const int* sizes);
ValueObject(const int**** data, const int* sizes);
ValueObject(const int***** data, const int* sizes);
ValueObject(const int****** data, const int* sizes);
ValueObject(const int******* data, const int* sizes);
ValueObject(const int******** data, const int* sizes);
ValueObject(const int********* data, const int* sizes);
ValueObject(const int********** data, const int* sizes);
usw.
aber dann steht man immer vor dem Dilemma, dass man entweder wahnsinnig viel Code hat oder es doch irgendwem noch eine Dimension zu wenig ist und der Spaß würde sich natürlich für alle Datentypen wiederholen, also brauche ich besseren Approach.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 30.11.2010, 20:38
von Krishty
kaiserludi hat geschrieben:wobei sizes ein eindimensionaler array wäre mit so vielen Elementen, wie Parameter 1 an Dimensionen hat.
Du kannst herausfinden, wie viele Dimensionen das Array hat und falls es statisch ist, kannst du auch die Länge jeder Dimension bestimmen. Aber du kannst dir daraus
nicht einen Parameter füllen lassen – diese Information steht dir nur in statischer Form zur Verfügung und du kannst sie nur rekursiv verarbeiten.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 30.11.2010, 20:51
von kaiserludi
Es geht mir mehr darum, wie ich dem Aufrufer erlauben kann, n-dimensionale int--arrays zu übergeben, ohne ihm zu erlauben, arrays beliebigen typs zu übergeben.
Was den die Längen angeht, komme ich bei c-arrays eh nicht drum herum, die durch den Aufrufer übergeben zu lassen.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 30.11.2010, 21:02
von Krishty
Achso. Jaa, ist ja ganz einfach:
Code: Alles auswählen
template <typename CType> struct TCConfirmAllowed;
template <> struct TCConfirmAllowed<int> { typedef int type; };
template <> struct TCConfirmAllowed<bool> { typedef bool type; };
// … usw. Wirst du ja so ähnlich schon für alle erlaubten Typen spezialisiert haben
template <typename CType> struct TCConfirmAllowed<CType *> { typedef typename TCConfirmAllowed<CType>::type type; };
…
class ValueObject {´
…
template <typename CType> ValueObject(typename TCConfirmAllowed<CType>::type data, int const * sizes);
Knifflig wird es aber, wenn c/v-Qualifier hineinkommen – der Code oben wird
int const * nicht als gültigen Typ erkennen. Damit also auch das akzeptiert würde, musst du erweitern:
Code: Alles auswählen
template <> struct TCConfirmAllowed<int const> { typedef int const type; };
template <> struct TCConfirmAllowed<int volatile> { typedef int volatile type; };
template <> struct TCConfirmAllowed<int const volatile> { typedef int const volatile type; };
Und damit dasselbe nochmal mit Zeigern geht:
Code: Alles auswählen
template <typename CType> struct TCConfirmAllowed<CType * const> { typedef typename TCConfirmAllowed<CType>::type * const type; };
template <typename CType> struct TCConfirmAllowed<CType * volatile> { typedef typename TCConfirmAllowed<CType>::type * volatile type; };
template <typename CType> struct TCConfirmAllowed<CType * const volatile> { typedef typename TCConfirmAllowed<CType>::type * const volatile type; };
Oder alternativ ein Template, das dir die C/V-Qualifier automatisch rausnimmt und wieder anfügt, schreiben.
Noch ein Stolperdraht: Nicht-Template-Funktionen werden
immer bevorzugt. Das bedeutet: Würdest du irgendwann mal einen K’tor mit
void *-Parameter hinzufügen, würde das Template nie mehr aufgerufen.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 14.12.2010, 17:23
von kaiserludi
So, konnte mich ein paar Tage nicht mit diesem Thema beschäftigen, jetzt bin ich wieder dran und habe gleich die nächste Frage dazu:
Ein Aufruf wie folgt:
Code: Alles auswählen
ev.put(KeyObject<JString>("test"), ValueObject<TCConfirmAllowed<nByte**>>(TCConfirmAllowed<nByte**>::type((nByte**)NULL), 3, (short*)NULL));
schmeißt mir die Fehlermeldung
error C2661: 'ValueObject<Etype>::ValueObject' : no overloaded function takes 3 arguments
obwohl die Klasse nun folgenden Konstruktor deklariert:
Code: Alles auswählen
template <typename CType> ValueObject(typename TCConfirmAllowed<CType>::type data, int dimension, const short* sizes);
Da scheine ich syntaktisch noch nicht ganz durch zu blicken.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 14.12.2010, 18:59
von BeRsErKeR
kaiserludi hat geschrieben:Ein Aufruf wie folgt:
Code: Alles auswählen
ev.put(KeyObject<JString>("test"), ValueObject<TCConfirmAllowed<nByte**>>(TCConfirmAllowed<nByte**>::type((nByte**)NULL), 3, (short*)NULL));
Kann mich irren aber muss das nicht so heißen?
Code: Alles auswählen
ev.put(KeyObject<JString>("test"), ValueObject<nByte**>(TCConfirmAllowed<nByte**>::type((nByte**)NULL), 3, (short*)NULL));
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 14.12.2010, 19:08
von kaiserludi
BeRsErKeR hat geschrieben:
Kann mich irren aber muss das nicht so heißen?
Code: Alles auswählen
ev.put(KeyObject<JString>("test"), ValueObject<nByte**>(TCConfirmAllowed<nByte**>::type((nByte**)NULL), 3, (short*)NULL));
Habe ich auch ausprobiert, aber führt zur gleichen Fehlermeldung.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 14.12.2010, 19:15
von Krishty
(BeRsErKeR hat recht, afaik kann man außerdem die explizite Angabe des <nByte**>-Template-Parameters weglassen, weil sie der Compiler selber auflösen können müsste.)
Was ist das Problem an der Fehlermeldung? Du übergibst drei Parameter, aber hast keinen Konstruktor definiert, der drei annimmt. In dem Post oben, wo du ValueObject definiert hast, nehmen die Konstruktoren auch nur entweder einen oder zwei Parameter. Das (short*)NULL kommt aus dem Nichts.
Vor das TCConfirmAllowed<nByte**>::type((nByte**)NULL) muss afaik auch noch ein typename, sonst weiß der Compiler nicht, ob es sich nciht vielleicht um einen Funktionsaufruf handelt.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 14.12.2010, 21:09
von kaiserludi
Wenn ich die explizite Angabe des template-Parameters weglasse, dann meckert VS "Use of class template requires template argument list".
Zu den 3 Parametern:
War ein Verständnisproblem, ich dachte, mit der Zeile:
"template <typename CType> ValueObject(typename TCConfirmAllowed<CType>::type data, int dimension, const short* sizes);" hätte ich einen Konstruktor deklariert, der 3 Parameter nimmt.
ich habe nun die Klasse um folgende Konstruktoren erweitert:
Code: Alles auswählen
ValueObject(const nByte** data, int dimensions, short* sizes);
ValueObject(const int** data, int dimensions, short* sizes);
ValueObject(const JString** data, int dimensions, short* sizes);
ValueObject(const int64** data, int dimensions, short* sizes);
ValueObject(const bool** data, int dimensions, short* sizes);
ValueObject(const short** data, int dimensions, short* sizes);
ValueObject(const float** data, int dimensions, short* sizes);
ValueObject(const double** data, int dimensions, short* sizes);
Den Aufruf versteht er jetzt auch:
Code: Alles auswählen
ev.put(KeyObject<JString>("test"), ValueObject<nByte**>((const nByte**)NULL, 3, (short*)NULL));
aber dort kommt TCConfirmAllowed auch noch nicht zum Einsatz.
Blooß wie würde ich jetzt einen 3-dimensionalen nByte-array übergeben, also nByte*** ?
Bei dem Code
Code: Alles auswählen
ev.put(KeyObject<JString>("test"), EValueObject<nByte***>(TCConfirmAllowed<nByte***>::type((const nByte***)NULL), 3, (short*)NULL));
vermeldet VS "none of the 28 overloads could convert all the argument types while trying to match the argument list '(unsigned char, int, short *)"
und bei
Code: Alles auswählen
ev.put(KeyObject<JString>("test"), ValueObject<nByte**>((const nByte***)NULL, 3, (short*)NULL));
heißt es "none of the 28 overloads could convert all the argument types while trying to match the argument list '(const nByte ***, int, short *)"
Lasst euch btw. nicht durch das nByte verwirren, dass ist einfach nur als unsigned char definiert.
Vor das TCConfirmAllowed<nByte**>::type((nByte**)NULL) muss afaik auch noch ein typename, sonst weiß der Compiler nicht, ob es sich nciht vielleicht um einen Funktionsaufruf handelt.
wie sähe das denn syntaktisch im Code aus?
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 14.12.2010, 22:14
von Krishty
kaiserludi hat geschrieben:ich habe nun die Klasse um folgende Konstruktoren erweitert:
Code: Alles auswählen
ValueObject(const nByte** data, int dimensions, short* sizes);
ValueObject(const int** data, int dimensions, short* sizes);
ValueObject(const JString** data, int dimensions, short* sizes);
ValueObject(const int64** data, int dimensions, short* sizes);
ValueObject(const bool** data, int dimensions, short* sizes);
ValueObject(const short** data, int dimensions, short* sizes);
ValueObject(const float** data, int dimensions, short* sizes);
ValueObject(const double** data, int dimensions, short* sizes);
WT… war es nicht gerade dein Ziel, sowas – also eine Version für jede mögliche Dimension – genau
nicht schreiben zu müssen?
kaiserludi hat geschrieben:Blooß wie würde ich jetzt einen 3-dimensionalen nByte-array übergeben, also nByte*** ?
Bei dem Code
Code: Alles auswählen
ev.put(KeyObject<JString>("test"), EValueObject<nByte***>(TCConfirmAllowed<nByte***>::type((const nByte***)NULL), 3, (short*)NULL));
vermeldet VS "none of the 28 overloads could convert all the argument types while trying to match the argument list '(unsigned char, int, short *)"
und bei
Code: Alles auswählen
ev.put(KeyObject<JString>("test"), ValueObject<nByte**>((const nByte***)NULL, 3, (short*)NULL));
heißt es "none of the 28 overloads could convert all the argument types while trying to match the argument list '(const nByte ***, int, short *)"
Krishty hat geschrieben:Code: Alles auswählen
class ValueObject {´
…
template <typename CType> ValueObject(typename TCConfirmAllowed<CType>::type data, int const * sizes);
In diesem Konstruktor kannst du auf den Array-Typ per
CType zugreifen, auf die Array-Daten per
data. Deinen Dimensionsparameter musst du natürlich noch hinzufügen, aber ich würde ihn besser automatisch durch das Template bestimmen lassen. Ebenso würde ich den originalen Datentyp (also das
int oder
nByte vor den
***) vom Template bestimmen lassen, das dürfte alles nochmal einfacher machen.
kaiserludi hat geschrieben:Vor das TCConfirmAllowed<nByte**>::type((nByte**)NULL) muss afaik auch noch ein typename, sonst weiß der Compiler nicht, ob es sich nciht vielleicht um einen Funktionsaufruf handelt.
wie sähe das denn syntaktisch im Code aus?
Indem vor dem
TCConfirmAllowed<nByte**>::type((nByte**)NULL) noch ein
typename stünde? ;)
Btw – bei 28 Konstruktoren hoffe ich, dass du niemals mehr was an der Klasse ändern müssen wirst.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 15.12.2010, 11:49
von kaiserludi
Krishty hat geschrieben:kaiserludi hat geschrieben:ich habe nun die Klasse um folgende Konstruktoren erweitert:
Code: Alles auswählen
ValueObject(const nByte** data, int dimensions, short* sizes);
ValueObject(const int** data, int dimensions, short* sizes);
ValueObject(const JString** data, int dimensions, short* sizes);
ValueObject(const int64** data, int dimensions, short* sizes);
ValueObject(const bool** data, int dimensions, short* sizes);
ValueObject(const short** data, int dimensions, short* sizes);
ValueObject(const float** data, int dimensions, short* sizes);
ValueObject(const double** data, int dimensions, short* sizes);
WT… war es nicht gerade dein Ziel, sowas – also eine Version für jede mögliche Dimension – genau
nicht schreiben zu müssen?
Ja, war es und ist es auch immer noch. Bloß macht es natürlich wenig Sinn, einen size-array als Parameter zu verlangen für 1D-Arrays, da reicht auch ein einfacher short für die size (nicht int, da über Netz nur 2 byte gesendet werden für die size).Daher muss also ein Overload für 2d schon noch sein, aber ab 3d kann ichs mir dann sparen und dennoch kann man auch 1000D-arrays übergeben, wenn jemand das unbedingt will.
Krishty hat geschrieben:
kaiserludi hat geschrieben:Blooß wie würde ich jetzt einen 3-dimensionalen nByte-array übergeben, also nByte*** ?
Bei dem Code
Code: Alles auswählen
ev.put(KeyObject<JString>("test"), EValueObject<nByte***>(TCConfirmAllowed<nByte***>::type((const nByte***)NULL), 3, (short*)NULL));
vermeldet VS "none of the 28 overloads could convert all the argument types while trying to match the argument list '(unsigned char, int, short *)"
und bei
Code: Alles auswählen
ev.put(KeyObject<JString>("test"), ValueObject<nByte**>((const nByte***)NULL, 3, (short*)NULL));
heißt es "none of the 28 overloads could convert all the argument types while trying to match the argument list '(const nByte ***, int, short *)"
Krishty hat geschrieben:Code: Alles auswählen
class ValueObject {´
…
template <typename CType> ValueObject(typename TCConfirmAllowed<CType>::type data, int const * sizes);
In diesem Konstruktor kannst du auf den Array-Typ per
CType zugreifen, auf die Array-Daten per
data. Deinen Dimensionsparameter musst du natürlich noch hinzufügen, aber ich würde ihn besser automatisch durch das Template bestimmen lassen. Ebenso würde ich den originalen Datentyp (also das
int oder
nByte vor den
***) vom Template bestimmen lassen, das dürfte alles nochmal einfacher machen.
Typ und Dimensionszahl vom Template bestimmen lassen klingt vielversprechend. Das heißt, das Template kann herausfinden, die Version mit wie vielen * am ersten Parameter sie ist?
Krishty hat geschrieben:
kaiserludi hat geschrieben:Vor das TCConfirmAllowed<nByte**>::type((nByte**)NULL) muss afaik auch noch ein typename, sonst weiß der Compiler nicht, ob es sich nciht vielleicht um einen Funktionsaufruf handelt.
wie sähe das denn syntaktisch im Code aus?
Indem vor dem
TCConfirmAllowed<nByte**>::type((nByte**)NULL) noch ein
typename stünde? ;)
Btw – bei 28 Konstruktoren hoffe ich, dass du niemals mehr was an der Klasse ändern müssen wirst.
Ah, ok, das Schlüsselwort "typename", ich hatte verstanden, ich solle z.B. "int" davor schreiben und konnte mir nicht vorstellen, wie das funzen soll, aber mit "typename" macht es Sinn.
Die Klasse besteht auch fast nur aus Konstruktoren, ansonsten gibts nur noch getDataCopy(), getDataAdress(), eine private Helpermethode und einen Destruktor.
EDIT:
Krishty hat geschrieben:
kaiserludi hat geschrieben:Vor das TCConfirmAllowed<nByte**>::type((nByte**)NULL) muss afaik auch noch ein typename, sonst weiß der Compiler nicht, ob es sich nciht vielleicht um einen Funktionsaufruf handelt.
wie sähe das denn syntaktisch im Code aus?
Indem vor dem
TCConfirmAllowed<nByte**>::type((nByte**)NULL) noch ein
typename stünde? ;)
Hmm, da meint VS dann:
error C2899: typename cannot be used outside a template declaration
EDIT2: fixed quotes von Edit1
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 15.12.2010, 12:53
von Krishty
kaiserludi hat geschrieben:Bloß macht es natürlich wenig Sinn, einen size-array als Parameter zu verlangen für 1D-Arrays, da reicht auch ein einfacher short für die size (nicht int, da über Netz nur 2 byte gesendet werden für die size).Daher muss also ein Overload für 2d schon noch sein, aber ab 3d kann ichs mir dann sparen und dennoch kann man auch 1000D-arrays übergeben, wenn jemand das unbedingt will.
Okay. Falls du irgendwann den Überladungsdruck senken willst, sehe ich da aber eine Möglichkeit.
kaiserludi hat geschrieben:Typ und Dimensionszahl vom Template bestimmen lassen klingt vielversprechend. Das heißt, das Template kann herausfinden, die Version mit wie vielen * am ersten Parameter sie ist?
Ja, genau – und, wessen Typs die finalen Array-Elemente sind. Du musst dazu
template <> struct TCConfirmAllowed<int> { typedef int type; }; zu
Code: Alles auswählen
template <> struct TCConfirmAllowed<int> {
typedef int type;
typedef int scalarType;
static size_t const dimension = 0; // Was kein Array ist, ist 0-dimensional
};
umschreiben, und die beiden neuen Attribute bei den anderen erlaubten Typen ebenfalls hinzufügen. Die (fehlerhaften, siehe unten!) Zeiger-Versionen
template <typename CType> struct TCConfirmAllowed<CType *> { typedef typename TCConfirmAllowed<CType>::type type; }; erweiterst du durch
Code: Alles auswählen
template <typename CType> struct TCConfirmAllowed<CType *> {
typedef typename TCConfirmAllowed<CType>::type * type; // ACHTUNG siehe unten
typedef typename TCConfirmAllowed<CType>::scalarType scalarType; // Skalaren Typ von der skalaren Version übernehmen
static size_t const dimension = TCConfirmAllowed<CType>::dimension + 1; // Eine Dimension mehr als der dereferenzierte Typ
};
Und, dabei fällt mir auf –
wichtig: das t
ypedef typename TCConfirmAllowed<CType>::type type; in den oberen Posts war falsch, da muss (wie in dem Code-Stück direkt hier drüber) noch der Zeiger-Deklarator zwischen
e>::type und
type. Bei den c/v-Versionen muss da das entsprechende
* const,
* volatile und
* const volatile zwischen. Vielleicht funktioniert es deshalb nicht.
(Das sollte dann auch der Punkt sein, an dem ich es mir selber nachbaue statt alles aus dem Kopf zu machen.)
kaiserludi hat geschrieben:Hmm, da meint VS dann:
error C2899: typename cannot be used outside a template declaration
Weil du noch in dem nicht-ge-template-ten Konstruktor bist. Sobald er ein Template ist, muss das
typename dahin.
————
Soo, mal selber nachgebaut – eine Funktion, die
int,
int*,
int** usw akzeptiert und die Dimension ausgibt:
Code: Alles auswählen
#include <iostream>
template <
typename CType
> struct TCConfirmAllowed;
template <> struct TCConfirmAllowed<int> {
typedef int type;
typedef int scalarType;
static size_t const dimension = 0;
};
template <
typename CType
> struct TCConfirmAllowed<CType *> {
typedef TCConfirmAllowed<CType> dereferencedInfo;
typedef typename dereferencedInfo::type * type;
typedef typename dereferencedInfo::scalarType scalarType; // Skalaren Typ von der skalaren Version übernehmen
static size_t const dimension = dereferencedInfo::dimension + 1; // Eine Dimension mehr als der dereferenzierte Typ
};
template <
typename CToBePrinted
> void printDimension(
CToBePrinted const &
) {
::std::cout << TCConfirmAllowed<CToBePrinted>::dimension << '\n';
return;
}
…
int x;
::std::cout << "dimension of x: "; printDimension(x); // "dimension of x: 0"
int** xpp;
::std::cout << "dimension of xpp: "; printDimension(xpp); // "dimension of xpp: 2"
char * cp;
::std::cout << "dimension of cp: "; printDimension(cp); // error C2027: use of undefined type 'TCConfirmAllowed<CType>'; see reference to class template instantiation 'TCConfirmAllowed<CType>' being compiled; see reference to function template instantiation 'void printDimension<char*>(const CToBePrinted &)' being compiled
Per rekursivem Funktionstemplate wäre es dann noch möglich, z.B. das Array automatisch auszudrucken.
TCConfirmAllowed<CToBePrinted>::type als Parameter hinzuschreiben konnte der Compiler tatsächlich nicht automatisch auflösen, da hänge ich atm noch dran.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 10.02.2011, 13:34
von kaiserludi
Hmm, ich checke immer noch nicht, wie jetzt der Kontruktoraufruf für z.B. einen 3-dimensionalen Array aussehen würde.
Bei Konstruktionen wie dieser
ValueObject<int***>((const int***)NULL, (const short*)NULL);
heißt es mal wieder "non of the x overloads could convert all argument types"
Kontruktionen mit TCConfirmAllowed im Aufruf scheitern grundsätzlich daran, dass TCConfirmAllowed hier für den Compiler ein undeclared identifier ist.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 10.03.2011, 17:41
von kaiserludi
So, habe mich nochmal etwas intensiver mit dem Problem auseinander gesetzt:
Code: Alles auswählen
template <typename CType> ValueObject(typename TCConfirmAllowed<CType>::type data, int const * sizes);
Dieser Konstruktor ist zwar standkonform definierbar, aber überhaupt nicht aufrufbar.
Wenn Funktionsparameter und Templateparamter eines Funktionstemplates nicht übereinstimmen, dann kann der Compiler nicht automatisch am Aufruf ermitteln, für welchen Typ er eine Funktion erstellen musst, weshalb man in diesem Fall explizit beim Aufruf den Templateparameter mit angeben muss.
Leider ist es aber syntaktisch nicht möglich, einen Konstruktor mit Templateparametern aufzurufen, da man ihn gar nicht wirklich selsbt aufruft, sondern dies beim erstellen des Objektes automatisch passiert.
Hier das ganze mal als Testcode, den ihr selbser versuchen könnt, aufzurufen:
Code: Alles auswählen
class Object1:public Object
{
public:
template<typename CType> void test(CType data)
{
// can be called without explicit template argument: foo.test(temp);
}
template<typename CType> Object1(CType data)
{
// can be called without explicit template argument: Object1 foo(temp);
}
};
class Object2:public Object
{
public:
template<typename CType> void test(typename TCConfirmAllowed<CType>::type data)
{
// can only be called with explicit template argument: foo.test<int>(temp);
}
template<typename CType> Object2(typename TCConfirmAllowed<CType>::type data)
{
// CAN NOT BE CALLED AT ALL!
}
};
und hier noch die Quelle:
http://learningcppisfun.blogspot.com/20 ... licit.html
hat wer eine alternative Idee zur Lösung des ursprünglichen Problems?
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 10.03.2011, 18:28
von kaiserludi
OK, wenn ich den Kontruktor nicht als Funktionstemplate, sondern als normale Funktione anlege, reicht das in meinem Fall auch, da ich als Templateparameter für TCConfirmAllowed eh immer nur den Templateparameter der Klasse übergeben muss.
Folgendes funktioniert:
Code: Alles auswählen
template<typename Etype>
class VObject:public Object
{
public:
VObject(typename TCConfirmAllowed<Etype>::type data)
{
}
};
Ein 4 dimensionaler int-array wird wie gewünschtvom Compiler akzeptiert:
Ein char hingegen wird wie gewünscht nicht ohne expliziten cast akzeptiert
ok, nen char als int übergeben, funktioniert natürlich, aber nur, weil der Compiler implizit casten kann, das Objekt irgendeiner beliebigen nicht unterstützen Klasse bekomme ich so wie gewünscht nicht rein:
Der Code allerdings verhält sich nicht ganz so, wie man es erwarten würde:
Das führt nämlich zu "fatal error C1001: An internal error has occurred in the compiler." inklusive Windows-Prograamabsturz-Messagebox, dass der automatische Codeoptimierer einen Crash hatte unn dem Hinweis im VS Output, ich sollte meinen Code simplifizieren inder letzen Zeile folgendes Klassentremplates:
Code: Alles auswählen
template<class CType> struct TCConfirmAllowed<CType*>
{
typedef typename TCConfirmAllowed<CType>::type* type;
typedef typename TCConfirmAllowed<CType>::scalarType scalarType;
static size_t const dimension = TCConfirmAllowed<CType>::dimension+1;
};
Visual Studio kommt hier offensichtlich nicht mit dem dimension+1 im Template für CType* klar, wenn kein Template für CType existiert.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 11.03.2011, 14:03
von kaiserludi
Ich habe jetzt neben den ConfirmAllowed-templates für die sklalaren Datentypen auch folgende definiert:
Code: Alles auswählen
template<class CType> struct ConfirmAllowed<const CType> // const version
{
typedef typename ConfirmAllowed<CType>::type type;
typedef typename ConfirmAllowed<CType>::scalarType scalarType;
static const unsigned int dimensions = ConfirmAllowed<CType>::dimensions;
static const nByte typeName = ConfirmAllowed<CType>::typeName;
};
template<class CType> struct ConfirmAllowed<CType*> // pointer version
{
typedef typename ConfirmAllowed<CType>::type* type;
typedef typename ConfirmAllowed<CType>::scalarType scalarType;
static const unsigned int dimensions = ConfirmAllowed<CType>::dimensions+1;
static const nByte typeName = ConfirmAllowed<CType>::typeName;
};
template<class CType> struct ConfirmAllowed<const CType*> // pointer to const version
{
typedef typename ConfirmAllowed<CType*>::type type;
typedef typename ConfirmAllowed<CType*>::scalarType scalarType;
static const unsigned int dimensions = ConfirmAllowed<CType*>::dimensions+1;
static const nByte typeName = ConfirmAllowed<CType*>::typeName;
};
Die ersten beiden funzen auch wie gedacht (von dem internal Compiler Error, für den Aufruf der Pointervariante, wenn die skalare Veriante nicht existiert mal abgesehen), letzterer aber nicht.
Folgender Aufruf funktioniert:
Der hier hingegen
Code: Alles auswählen
ValueObject<const int*>((const int*)NULL, (short*)NULL);
führt zum allseits beliebten "non of the overloads could convert all argument types"-error und dem Compiler Output nach, welche Overloads er in Betracht zieht, sieht er gar keine pointer to const Variante, aber nach wie vor die pointer Variante, die für Etype == const int* eigentlich gar nicht existieren sollte, schließlich existiert für Etype == const int auch die const Variante anstatt der non-const Variante.
Wie muss ich vorgehen, um das ganze für const int* zum Laufen zu bekommen?
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 11.03.2011, 14:08
von Krishty
Ich bin, obwohl selber geschrieben, leider viel zu weit da raus – aber sollte ConfirmAllowed<…>::type nicht den Originaltyp wiederspiegeln? In dem Fall muss in der const-Version auch typedef typename ConfirmAllowed<CType>::type const type; stehen. Sonst geht in das Template ein const-qualifizierter Typ rein, aber ein nicht-const-qualifizierter heraus.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 11.03.2011, 14:38
von kaiserludi
Tatsächlich, das hatte ich übersehen, hat auch beim scalaren Const gefehlt, dort aber keine Probleme verursacht, wodruch es mir nicht aufgefallen ist, dann beim const* wurde es dann scheinbar zum Problem.
Korrekt ist der Code also wie folgt:
Code: Alles auswählen
template<class CType> struct ConfirmAllowed<const CType>
{
typedef typename const ConfirmAllowed<CType>::type type;
typedef typename const ConfirmAllowed<CType>::scalarType scalarType;
static const unsigned int dimensions = ConfirmAllowed<CType>::dimensions;
static const nByte typeName = ConfirmAllowed<CType>::typeName;
};
template<class CType> struct ConfirmAllowed<CType*>
{
typedef typename ConfirmAllowed<CType>::type* type;
typedef typename ConfirmAllowed<CType>::scalarType scalarType;
static const unsigned int dimensions = ConfirmAllowed<CType>::dimensions+1;
static const nByte typeName = ConfirmAllowed<CType>::typeName;
};
template<class CType> struct ConfirmAllowed<const CType*>
{
typedef typename const ConfirmAllowed<CType>::type* type;
typedef typename const ConfirmAllowed<CType>::scalarType scalarType;
static const unsigned int dimensions = ConfirmAllowed<CType>::dimensions+1;
static const nByte typeName = ConfirmAllowed<CType>::typeName;
};
Man beachte im letzten Block, dass es "typedef typename const ConfirmAllowed<CType>::type* type;" heißen muss, um für einen const* zu funktionieren. Wenn man "typedef typename const ConfirmAllowed<CType*>::type type;" schreibt, wird daraus interessanterweise ein *const, obwohl cas "const" links vom "*" steht.
Alternativ funzt auch:
Code: Alles auswählen
template<class CType> struct ConfirmAllowed<const CType*>
{
typedef typename ConfirmAllowed<const CType>::type* type;
typedef typename ConfirmAllowed<const CType>::scalarType scalarType;
static const unsigned int dimensions = ConfirmAllowed<const CType>::dimensions+1;
static const nByte typeName = ConfirmAllowed<const CType>::typeName;
};
was ich optisch ein wenig eleganter empfinde, da die pointer to const Variante sich ja genauso zur const Variante verhält wie die pointer to nonconst Version zur nonconst Version.
So, scheint jetzt im Wesentlichen alles so zu funktionieren, wie es soll, auch wenn ich für das Laufzeitverhalten erstmal noch ein bischen Testcode schreiben werde, um sicher zu gehen.
Etwas unglücklich ist noch, dass es bisher nicht möglich ist, Typen zu haben, die nur als skalare Variante erlaubt sind und nicht als arrays, da sich die Klasse nicht kompilieren lässt, wenn für einen Type eine Spezialisierung des ValueObject-Templates existiert, aber keine Varainte des ConfirmAllowed-Templates, aber damit werde ich leben können.
Vielen Dank, Krishty.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 11.03.2011, 14:57
von Krishty
kaiserludi hat geschrieben:Man beachte im letzten Block, dass es "typedef typename const ConfirmAllowed<CType>::type* type;" heißen muss, um für einen const* zu funktionieren. Wenn man "typedef typename const ConfirmAllowed<CType*>::type type;" schreibt, wird daraus interessanterweise ein *const, obwohl cas "const" links vom "*" steht.
Ja,
typedef funktioniert für Typen wie Klammern für Terme:
typedef int * intptr;
const intptr; // evaluiert nicht zu "const int *", sondern zu "const (int *)"
Es ist also
nicht einfach ein
#define. Und ein Grund mehr, das
const als Postfix zu benutzen.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 29.03.2011, 12:14
von kaiserludi
Ich habe jetzt neben den Konstruktoren für die Basistypen auch diese beiden Konstruktoren:
Code: Alles auswählen
ValueObject(typename ConfirmAllowed<Etype>::type data, short size);
ValueObject(typename ConfirmAllowed<Etype>::type data, const short* const sizes);
Wenn ich jetzt den oberen Kosntruktor wie folgt aufrufen will:
dann bekomme ich in Visual Studio folgende Fehlermeldung:
error C2668: 'ValueObject<Etype>::ValueObject' : ambiguous call to overloaded function
with
[
Etype=int *
]
could be 'ValueObject<Etype>::ValueObject(int *,const short *const )'
with
[
Etype=int *
]
or 'ValueObject<Etype>::ValueObject(int *,short)'
with
[
Etype=int *
]
while trying to match the argument list '(int *, int)
Sollte der Compiler da nicht ohne expliziten Cast auf short* die short-Variante gegenüber der const short* cosnt Variante bevorzuge, wenn der Call ein int übergibt?
funzt natürlich wie erwartet.
Interessanterweise funzt aber auch folgendes ohne Fehlermeldung:
Sollte die hardcodet Konstante 0 nicht ein int sind und damit die Übergabe von 0 den gleichen Downcast verursachen wie die einer lokalen int-Variable?
Noch interessanter wird es, wenn man bedenkt, dass auch folgendes problemlos kompiliert:
Es funktioniert mit 1, aber nicht mit 0? 0 hat demnach einen anderen Standarddatentyp in Visual Studio als 1?
Kann mir wer erklären, was die Ursache dieses seltsamen Verhaltens ist?
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 29.03.2011, 12:45
von Aramis
Das ist gemein.
Das Token 0 hat eine Doppelbedeutung in C++ – es ist sowohl das Integer-Literal fuer 0 wie auch ein void*, dessen Wert einer fuer die Zielplattform invaliden Adresse entspricht. Es muesste theoretisch noch nicht einmal die Adresse 0 sein, auch wenn das in der Praxis immer der Fall sein duerfte.
Aus diesem Grunde ist der erste Aufruf zweideutig.
(Genauer: soweit ich weiss, ist das einer der beiden Faelle in C++03 an denen der Compiler so etwas wie Type-Inferenz betreibt und den Typ eines Ausdruckes am umliegenden Kontext abliest. Der explizite static_cast nach short macht dem Compiler also klar, was fuer einen Typ die 0 wirklich haben soll. Wie laecherlich das ist, sieht man schon an der Einfuehrung von nullptr mit C++ 2011 - allerdings entfaellt damit die alte Regel aus Kompatibilitaetsgruenden nicht).
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 29.03.2011, 13:36
von kaiserludi
Aramis hat geschrieben:
Es muesste theoretisch noch nicht einmal die Adresse 0 sein.
Urgs, sprich, ein NULL, welches in C als ((void*)0) und in C++ als 0 definiert ist, könnte z.B. auch auf die Speicheradresse 1 verweisen?
Demnach würde dann in dem Fall *(int*)(rand()%2) auf die identische Adresse zugreifen? OK, wäre wohl selbst, wenn ein Compiler das machen würde, nicht praxisrelevant, weil es mir ja Wurst sein kann, ob ich nen EXC_BAD_ACCESS für Adresse 0 oder Adresse 1 bekomme, aber weird ist es dennoch.
Zum eigentlicehn Thema deiner Antwort:
Hmm, man könnte doch in C++ einfach NULL wie in C als ((void*)0) definieren und 0 ohen expliziten void+-Cast immer als int integer-literal interpretieren, bzw. man hätte es bei EInführung des Standards gekonnt, vermutlich gibts inzwischen zu viele Programme, die den Literal übergeben statt einer Konstante für den Nullpointer.
OK, dann weiß ich jetzt zumindest den Hintergrund und das die Problematik nur bei Kombination von Strinliteralen für die Größenangabe und eindimensionalen Arrays der Größe 0 vorkommt. Diese Kombiantion sollte wohl kein all zu häufig vorkommender Fall in der Praxis sein. In dem Fall muss der Benutzer der Klasse dann eben casten, dann kann ich mir Spezialisierungen für eindimensionale Arrays für jeden einzelenen unterstützten Datentyp sparen und brauche sie nur für die Skalartypen.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 29.03.2011, 14:10
von Krishty
kaiserludi hat geschrieben:Urgs, sprich, ein NULL, welches in C als ((void*)0) und in C++ als 0 definiert ist, könnte z.B. auch auf die Speicheradresse 1 verweisen?
Ja; in einigen Unix-Treibern zeigt
0 (und damit auch
nullptr) ganz woanders hin, weil auf ein paar Systemen die tatsächliche Adresse
0x00000000 durch den gültigen Adressbereich des Kernels abgedeckt und damit nutzbar ist.
kaiserludi hat geschrieben:Demnach würde dann in dem Fall *(int*)(rand()%2) auf die identische Adresse zugreifen?
Nein, du kannst diese Adressen nicht vergleichen. Adressen, die nicht per
malloc() oder
new reserviert sind (plus ein Byte),
existieren in C und C++ nicht. Sie sind einfach nicht da. Beide ungültig zwar; aber wenn du es so ausdrückst, versuchst du, etwas nicht existentes mit etwas nicht existentem zu vergleichen.
Es ist ziemlich wichtig, zu verstehen, dass C++’ Zeiger rein symbolisch sind. Sie zeigen nicht auf
Adressen, sondern auf
Objekte oder auf garnichts (
nullptr). (Wenn man das begriffen hat, verhaspelt man sich auch nicht mehr bei Zeigerarithmetik.) Sie könnten auch ganz anders realisiert sein als durch Adressen. Es gibt Architekturen, wo
void * und
int * unterschiedlich groß sind.
Über Speicheradressen nachzudenken hat nichts mit C++-Programmierung zu tun, sondern ist Low-Level-Kram, der in 99 % der Fälle unbedeutend ist. Siehe auch:
The Stack Is An Implementation Detail.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 29.03.2011, 15:10
von kaiserludi
Krishty hat geschrieben: Über Speicheradressen nachzudenken hat nichts mit C++-Programmierung zu tun, sondern ist Low-Level-Kram, der in 99 % der Fälle unbedeutend ist.
Ich darf mich in der Praxis auch genug mit Low-Level C-Code und ab und an Inline-Assembler rumschlagen, da ist es schon wichtig, zu wissen, dass Pointer über Speicherbereiche realisiert sind, um Code wie pArray+i*sizeof(int*) nachvollziehen zu können.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 29.03.2011, 15:37
von Krishty
Dann hast du mir was voraus, denn ich kann den nicht wirklich nachvollziehen ;)
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 29.03.2011, 16:35
von kaiserludi
pArray+i*sizeof(int) ist highlevel im Grunde das Gleiche wie &pArray, wenn pArray als "int* pArray;" definiert ist. Allerdings funktioniert die erste Syntax auch, wenn man es mit einem "void* pArray;" zu tun hat, während die zweite Syntax in dem Fall einen explizen Cast auf int* benötigen würde, um zu wissen, wie viele Bytes im Speicher weiter gehüpft werden muss bis zum nächsten Array-Element, so dass es je nach Compiler Warning oder Error gibt, im ersten Fall undefiniertes Laufzeit-Verhalten, so dass man &((int*)pArray) schreiben müsste.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 29.03.2011, 16:50
von Krishty
kaiserludi hat geschrieben:Allerdings funktioniert die erste Syntax auch, wenn man es mit einem "void* pArray;" zu tun hat, während die zweite Syntax in dem Fall einen explizen Cast auf int* benötigen würde
Nein. Sie funktioniert nur, wenn
pArray auf
char zeigt, weil
void keine Größe hat. Außerdem dürfen Zeiger mit nichts als
char * und
unsigned char * überlappen, auch nicht mit
void * (
Strict Aliasing Rule). Glückwunsch; dein Text ist undefiniertes Verhalten.
kaiserludi hat geschrieben: Allerdings funktioniert die erste Syntax auch, wenn […], während die zweite Syntax in dem Fall einen explizen Cast auf int* benötigen würde […], so dass es je nach Compiler Warning oder Error gibt, im ersten Fall undefiniertes Laufzeit-Verhalten, so dass man &((int*)pArray) schreiben müsste.
Nochmal in mehr als einem Satz bitte :D Und
&((int*)pArray) gleicht dem klareren ((int*)pArray) + i.
Re: partielle Template Spezialisierung multi-dimension C-arr
Verfasst: 29.03.2011, 17:01
von kaiserludi
Es geht mir um so Code wie z.B. (Achtung, Pseudo-Code):
void* pArray = NULL;
if(condition)
{
pArray = malloc(sizeof(int)*length);
for(init i=0; i<length; i++)
foo(pArray+i*sizeof(int));
}
else
{
pArray = malloc(sizeof(short)*length);
for(init i=0; i<length; i++)
foo(pArray+i*sizeof(short));
}
So was darf ich öfter lesen und muss dann eben wissen, was das eigentlich macht, wozu wiederum relevant ist, wie arrays im Speicher implementiert sind und damit, dass Pointer lowlevel auf Speicheradressen zeigen.