[C++] template meta programming

Pagina: 1
Acties:

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Wat vinden jullie hier eigenlijk van? Heeft het tegenwoordig nog nut, of is het iets dat je beter niet kan doen om de leesbaarheid/aanpasbaarheid voor anderen te bevorderen?

Voor de grap zat ik even een template meta vector te maken; meer als oefening om het niet weg te laten zakken. Maar kwam dus op het volgende:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#define CompileTimeAssert(expr, msg) typedef char ERROR_##msg##[1][(expr)]

template <int N, typename ScalarT>
struct DotProduct {
    static ScalarT result(const ScalarT* lhs, const ScalarT* rhs) {
        return *lhs * *rhs + DotProduct<N-1, ScalarT>::result(lhs+1, rhs+1);
    }
};

template <typename ScalarT>
struct DotProduct<1, ScalarT> {
    static ScalarT result(const ScalarT* lhs, const ScalarT* rhs) {
        return *lhs * *rhs; 
    }
};

template <typename DestT, typename SrcT>
struct SumOperation {
    static void perform(DestT* dst, SrcT* src) {*dst += *src;}
};

template <int N, typename ScalarT, template <typename T1, typename T2> class Operation>
struct OperateSelf {
    static void result(ScalarT* dst, const ScalarT* src) {
        Operation<ScalarT, const ScalarT>::perform(dst, src);
        OperateSelf<N-1, ScalarT, Operation>::result(dst+1, src+1);
    }
};

template <typename ScalarT, template <typename T1, typename T2> class Operation>
struct OperateSelf<0, ScalarT, Operation> {
    static void result(ScalarT* dst, const ScalarT* src) {}
};

template <
    typename ScalarT,
    int Dim
>
class Vector {
public:
    Vector() {}
    ~Vector() {}

    ScalarT operator%(const Vector<ScalarT, Dim>& rhs) const {
        return DotProduct<Dim, ScalarT>::result(data_, rhs.data_);
//      float result = 0;
//      for (int i=0; i<Dim; ++i) {
//          result += data_[i] * rhs.data_[i];
//      }
//      return result;
    }


    Vector<ScalarT, Dim>& operator+=(const Vector<ScalarT, Dim>& rhs) {
        OperateSelf<Dim, ScalarT, SumOperation>::result(data_, rhs.data_);

//      for (int i=0; i<Dim; ++i) {
//          data_[i] += rhs.data_[i];
//      }

        return *this;
    }

    ScalarT& operator[](unsigned int i) {
        return data_[i];
    }

    const ScalarT& operator[](unsigned int i) const {
        return data_[i];
    }

    template <int Index>
    void set(ScalarT value) {
        CompileTimeAssert(Index < Dim, Vector_Index_out_of_range);
        data_[Index] = value;
    }

    template <int Index>
    ScalarT get() const {
        CompileTimeAssert(Index < Dim, Vector_Index_out_of_range);
        return data_[Index];
    }

private:
    ScalarT data_[Dim];
};


Nou blijkt dat voor de template code, en de commented runtime loops uiteindelijk exact dezelfde code wordt gegenereerd. Voor beiden voor de dot product nl.

GAS:
1
2
3
4
5
6
7
8
9
10
11
; 18   :    float a = v1 % v2;

    fld DWORD PTR _v2$[esp+40]
    fmul    DWORD PTR _v1$[esp+40]
    fld DWORD PTR _v2$[esp+36]
    fmul    DWORD PTR _v1$[esp+36]
    faddp   ST(1), ST(0)
    fld DWORD PTR _v2$[esp+32]
    fmul    DWORD PTR _v1$[esp+32]
    faddp   ST(1), ST(0)
    fstp    DWORD PTR _a$[esp+32]


Toen begon ik me ineens af te vragen waarom je tegenwoordig nog dit soort dingen zou gebruiken. Als een compiler nieuw/goed genoeg is om al die template code correct te compilen, dan is deze ook wel slim genoeg om zelf te optimizen. Eigenlijk neem je met meta programs het heft in eigen hand, en vertel je de compiler exact wat te doen. Maar ik denk dat compilers tegenwoordig het zelf veel beter kunnen.

Vroeg me af hoe hier over gedacht wordt...het blijft natuurlijk een leuke denk oefening :)

[ Voor 5% gewijzigd door Zoijar op 09-10-2004 13:56 ]


Verwijderd

Zoals je zegt is het zeker al een leuke oefening. Maar ik betwijfel of het veel nut heeft in een doorsnee toepassing. Moderne compilers kunnen heel wat doen voor jou wat betreft optimalisatie - en dan ga ik er van uit dat je code een beetje redelijk is natuurlijk. Als je bvb een lomp O(n^2) sorteer algoritme schrijft gaat je compiler er ook geen O(n log n) van maken vrees ik :P
Natuurlijk zijn er altijd wel dingen te bedenken waar je eigenlijk de laatste druppel optimalizatie eruit wil persen, en in dit geval kun je zelf toeren gaan uithalen. Maar ik denk dat in de meeste applicaties de voordelen van dit soort code niet opwegen tegen de nadelen: complexere code die moeilijk(er) leesbaar, onderhoudbaar en uitbreidbaar is. Want geef toe, je moet toch al redelijk overweg kunnen met C++ om zo'n code goed te gaan begrijpen. En die nadelen brengen dus een meerkost met zich mee. Software developers werken nou eenmaal niet voor niets ;)

  • Eelis
  • Registratie: Januari 2003
  • Laatst online: 21-02-2015
.

[ Voor 99% gewijzigd door Eelis op 18-02-2015 19:10 ]


Verwijderd

Het dotproduct is niet zo'n goed voorbeeld van de voordelen van metaprogramming omdat het een vector omrekent tot een scalair; metaprogramming is juist voordelig als we de elementen van de vector onafhankelijk van elkaar berekenen kunnen, zoals bv. bij vectoradditie of multiplicatie met een scalair.

In een object georienteerd model maakt het niets uit of je een dotproduct of een vectorsom berekent, er worden methods van het vectorobject aangeroepen met daarin een lus die de berekening voor alle elementen van de vector doorvoert en er wordt voor elke bewerking een tijdelijk object gegenereerd om de uitkomst in op te slaan. Dmv. metaprogramming dwing je de compiler al die kleine loopjes in een vectorexpressie terug te schrijven tot een grote loop en voorkomt die tijdelijke objecten, zo lang als de elementen van de vector onafhankelijk van elkaar zijn. Bij complexe vectorexpressies is het voor de compiler een zware taak om dat op de zelfde manier te optimizen.

C++:
1
2
Vector<double, 3> a, b, c, d, e;
double x= (5.0 * a + b) % (c * (d % e));

Wordt in een objectgeorienteerde implementatie (zonder sterke optimalisatie) iets als
C++:
1
2
3
4
5
6
7
8
9
10
double temp_5a[3];
for(int i= 0; i < 3; ++i) temp_5a[i]= 5.0 * a[i];
double temp_5a_b[3];
for(int i= 0; i < 3; ++i) temp_5a_b[i]= temp_5a[i] + b[i];
double temp_dedot= 0.0;
for(int i= 0; i < 3; ++i) temp_dedot+= d[i] * e[i];
double temp_c_dedot[3];
for(int i= 0; i < 3; ++i) temp_c_dedot[i]= c[i] * temp_dedot;
x= 0.0;
for(int i= 0; i < 3; ++i) x+= temp_5a_b[i] * temp_c_dedot[i];

Terwijl de templated implementatie dit doet (onafhankelijk van optimizing)
C++:
1
2
3
4
double temp_dedot= 0.0;
for(int i= 0; i < 3; ++i) temp_dedot+= d[i] * e[i];
x= 0.0;
for(int i= 0; i < 3; ++i) x+= (5.0 * a[i] + b[i]) * (c[i] * temp_dedot);

Dit is nog een redelijk eenvoudig voorbeeld, maar je kunt je voorstellen dat zelfs een goede optimizer problemen heeft om de code boven om te zetten tot de templated versie.

edit:
Een optimizer zal de code sequentieel optimaliseren, hetgeen voor de code boven dus iets als dit zou opleveren:
C++:
1
2
3
4
5
6
7
8
double temp_dedot= 0.0;
double temp_5a_b[3];
for(int i= 0; i < 3; ++i) {
    temp_5a_b[i]= 5.0 * a[i] + b[i];
    temp_dedot+= d[i] * e[i];
}
x= 0.0;
for(int i= 0; i < 3; ++i) x+= temp_5a_b[i] * (c[i] * temp_dedot);

Hetgeen je dus een extra temporary variable (temp_5a_b[3]) en complexere code in de maag splitst.

[ Voor 12% gewijzigd door Verwijderd op 09-10-2004 16:09 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Verwijderd schreef op 09 oktober 2004 @ 14:18:
Als je bvb een lomp O(n^2) sorteer algoritme schrijft gaat je compiler er ook geen O(n log n) van maken vrees ik :P
Ik ga welk uit van dezelfde algoritmes natuurlijk. Mijn punt was dat een compiler beter kan optimizen dan jij zelf, en TMP op een gegeven moment alleen maar de compiler in de weg zitten. Je laatste opmerkingen ben ik het wel mee eens.
Eelis schreef op 09 oktober 2004 @ 15:15:
Optimalisatie is slechts één van de mogelijke toepassingen van template metaprogramming, zie Boost voor diverse voorbeelden van nuttige TMP toepassingen die niet optimalisatie als hoofddoel hebben.
Ja, ik ken Boost uiteraard wel (maar voornamelijk Loki). Maar het lijkt een trend te worden om alles als TMP implementatie te schrijven. Zelf vind ik het ook leuk om te doen, maar de vraag is of het nou soms echt wel nuttig is. Of dat je je zelf in de vingers gaat snijden, omdat compilers hun werk niet meer goed kunnen doen.
Hmmm ik zie niet zo snel in of die laatste code ook echt trager wordt. Ik zal het wel eens proberen, en kijken wat de assembler output is. Maar ik snap het idee...
Maar wat als er nu bv vector computers overal beschikbaar zijn? Dan kan je compiler de TMP code waarschijnlijk niet optimizen daarvoor; terwijl de simpele code wellicht wel daar gebruik van kan maken?
Aan de andere kant geef je eigenlijk de compiler met TMP meer informatie, dus zou de resulterende code altijd uiteindelijk beter moeten worden of minstens gelijk.

Verwijderd

Zoijar schreef op 09 oktober 2004 @ 13:47:
Nou blijkt dat voor de template code, en de commented runtime loops uiteindelijk exact dezelfde code wordt gegenereerd.
Grappig, ik heb in het verleden precies hetzelfde gedaan, met dezelfde uitkomst. Was wel een beetje een domper: zat ik mezelf helemaal stoer te vinden omdat ik zulke ultra-gave template code had geschreven, bleek de simpele versie hetzelfde resultaat op te leveren!

Tegenwoordig heb ik een iets andere mening over hoe stoer dit soort code is. Tenzij het schrijven van complexe (template-) code duidelijke voordelen heeft wat betreft performance / herbruikbaarheid / whatever, moet je het gewoon niet doen. Het zijn nog steeds hele leuke dingen voor een regenachtige zondag middag, maar zeker als je in een team werkt met mensen die niet zo los zijn op templates, doe je niemand hier een plezier mee.

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Laat maar...

[ Voor 95% gewijzigd door Zoijar op 09-10-2004 22:14 ]


Verwijderd

Zoijar schreef op 09 oktober 2004 @ 19:04:
Hmmm ik zie niet zo snel in of die laatste code ook echt trager wordt.
In big-O notatie niet, maar door die extra temporary wordt de constante die je in big-O "wegintegreert" groter; in een vaak uitgevoerde inner loop zul je het verschil zeker merken.
Maar wat als er nu bv vector computers overal beschikbaar zijn? Dan kan je compiler de TMP code waarschijnlijk niet optimizen daarvoor; terwijl de simpele code wellicht wel daar gebruik van kan maken?
Ik denk dat dat weinig verschil maakt, de optimizer zal zowel bij de TMP code als bij de OO code slim genoeg moeten zijn om de loops die over de vectorelementen itereren te paralleliseren.
Aan de andere kant geef je eigenlijk de compiler met TMP meer informatie, dus zou de resulterende code altijd uiteindelijk beter moeten worden of minstens gelijk.
Exact, daar ligt de crux. Met TMP stel je de compiler de regels van het formele stelsel (de algebra) waarin je programma werkt beschikbaar, de optimizer heeft die informatie niet en kan alleen primaire logica toepassen.

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Toch heb ik net even iets getest, ik ga zo nog verder; maar bij een variabele length vector (dus zowel TMP, als for-loops) maakt de compiler code als: fld, fmul, fst, fld, fmul, fst... Terwijl met een directe, of eventueel recursive macro implementatie maakt hij er deze code van fld, fmul, fld, fmul, fst, fst. En dat is ietsjes sneller, omdat het beter pipelined. Ik zal zo jouw voorbeeld eens timen.

Haha, ik was aan het testen, init wat vectoren en deed er wat verschillende berkeningen mee, waarna ik de resultaten naar cout stuurden... Blijkt er in de code rechtstreeks 3 constanten naar cout te gaan, en thats it :) Dat is dan toch wel weer mooi :)

[ Voor 25% gewijzigd door Zoijar op 10-10-2004 19:57 ]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 17:05

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ik vind het iig leuk om mee te prutsen, maar erg nuttig is het in serieuze projecten niet echt. Ten eerste lopen de compiletimes gigantisch op (en hell, een volledige rebuild van Project: Snowblind duurt al 20 minuten op deze P4 3.2 GHz), ten tweede maakt de gegenereerde code totaal geen gebruik van SSE optimalisaties. We hebben hier een vrij straightforward vector en matrix lib die niet gebaseerd is op templates maar wel gebruik maakt van SSE compiler intrinsics. De temporaries die aangemaakt worden in een wat grotere expressies worden over het algemeen wel allemaal weggeoptimaliseert, hoewel exceptions wel uitgezet moeten worden omdat de functies anders niet geïnlined kunnen worden (daarnaast hebben exceptions in games toch niet zo gigantisch veel nut, behalve dat het je code bloat en een stuk langzamer maakt)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Sorry voor de lange post, dacht ik laat het even zien :)
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#ifndef LINALG_OPERATIONS_H_
#define LINALG_OPERATIONS_H_

#define CompileTimeAssert(expr, msg) typedef char ERROR_##msg##[1][(expr)]
#define LINALG_CALL_CONV __fastcall

namespace LinAlg {

template <typename, int> class Vector;

template <int N, int Wrap>
struct CircularAdd {
    enum {Result = N+1};
};

template <int Wrap>
struct CircularAdd<Wrap, Wrap> {
    enum {Result = 0};
};

/////////////////////////////////////

#define LINALG_OPERATION(NAME) \
struct NAME { \
    template <int Elem, typename ResultT, typename T1, int D1, typename T2, int D2> \
    inline static ResultT LINALG_CALL_CONV perform(const Vector<T1, D1>& lhs, const Vector<T2, D2>& rhs) { \

#define LINALG_END_OP \
} };

#define LINALG_FOLD_OPERATION(NAME) \
struct NAME { \
    template <typename ResultT, typename ScalarT> \
    inline static ResultT LINALG_CALL_CONV perform(const ScalarT& lhs, const ScalarT& rhs) { \

#define LINALG_VEC_BINARY_OPERATOR(CPP_OPERATOR, OPERATION) \
    template <typename T2, int Dim2> \
    inline Vector<ScalarT, Dim> LINALG_CALL_CONV CPP_OPERATOR##(const Vector<T2, Dim2>& rhs) const { \
        return Vector<ScalarT, Dim>(*this, rhs, OPERATION##()); \
    } \

#define LINALG_VEC_UNARY_OPERATOR(CPP_OPERATOR, OPERATION) \
    template <typename T2, int Dim2> \
    inline Vector<ScalarT, Dim>& LINALG_CALL_CONV CPP_OPERATOR(const Vector<T2, Dim2>& rhs) { \
        initop(*this, rhs, OPERATION##()); \
        return *this; \
    } \

//////////////////////////////////

LINALG_FOLD_OPERATION(OrFoldOperation)
    return static_cast<ResultT>(lhs || rhs);
LINALG_END_OP

LINALG_FOLD_OPERATION(SumFoldOperation)
    return static_cast<ResultT>(lhs + rhs);
LINALG_END_OP

LINALG_FOLD_OPERATION(AndFoldOperation)
    return static_cast<ResultT>(lhs && rhs);
LINALG_END_OP

LINALG_OPERATION(SumOperation)
    return static_cast<ResultT>(lhs.get<Elem>() + rhs.get<Elem>());
LINALG_END_OP

LINALG_OPERATION(ProductOperation)
    return static_cast<ResultT>(lhs.get<Elem>() * rhs.get<Elem>());
LINALG_END_OP

LINALG_OPERATION(DivisionOperation)
    return static_cast<ResultT>(lhs.get<Elem>() / rhs.get<Elem>());
LINALG_END_OP

LINALG_OPERATION(DifferenceOperation)
    return static_cast<ResultT>(lhs.get<Elem>() - rhs.get<Elem>());
LINALG_END_OP

LINALG_OPERATION(CrossProductOperation)
    CompileTimeAssert(D1 == 3 && D2 == 3, Cross_product_only_defined_for_dimension_three);
    const int X = CircularAdd<Elem, 2>::Result;
    const int Y = CircularAdd<X, 2>::Result;
    return static_cast<ResultT>(lhs.get<X>() * rhs.get<Y>() - lhs.get<Y>() * rhs.get<X>());
LINALG_END_OP

LINALG_OPERATION(NullOperation)
    return static_cast<ResultT>(0);
LINALG_END_OP

LINALG_OPERATION(SelectLeftOperation)
    return static_cast<ResultT>(lhs.get<Elem>());
LINALG_END_OP

LINALG_OPERATION(EqualsOperation)
    return static_cast<ResultT>(lhs.get<Elem>() == rhs.get<Elem>());
LINALG_END_OP

LINALG_OPERATION(NotEqualsOperation)
    return static_cast<ResultT>(lhs.get<Elem>() != rhs.get<Elem>());
LINALG_END_OP

} // namespace LinAlg

#endif

#ifndef LINALG_VECTOR_H_
#define LINALG_VECTOR_H_

#include "operations.h"

namespace LinAlg {

template <int N, typename Operation>
struct LoopOverAllElements {
    template <typename ScalarT1, int Dim1, typename ScalarT2, int Dim2, typename ScalarT3, int Dim3>
    static void loop(Vector<ScalarT1, Dim1>& dst, const Vector<ScalarT2, 
Dim2>& lhs, const Vector<ScalarT3, Dim3>& rhs) {
        dst.set<Dim1-N>(Operation::perform<Dim1-N, ScalarT1>(lhs, rhs));
        LoopOverAllElements<N-1, Operation>::loop(dst, lhs, rhs);
    }
};

template <typename Operation>
struct LoopOverAllElements<0, Operation> {
    template <typename ScalarT1, int Dim1, typename ScalarT2, int Dim2, typename ScalarT3, int Dim3>
    static void loop(Vector<ScalarT1, Dim1>& dst, const Vector<ScalarT2, 
Dim2>& lhs, const Vector<ScalarT3, Dim3>& rhs) {
    }
};

template <int N, typename FoldingOperation, typename CombineOperation>
struct FoldAllElements {
    template <typename ResultT, typename T1, int D1, typename T2, int D2> 
    static ResultT fold(const Vector<T1, D1>& lhs, const Vector<T2, D2>& rhs) {
        CompileTimeAssert(D1 >= N && D2 >= N, Fold_vector_too_small);
        return FoldingOperation::perform<ResultT, T1>(
            CombineOperation::perform<N-1, ResultT>(lhs, rhs),
            FoldAllElements<N-1, FoldingOperation, CombineOperation>::fold<ResultT>(lhs, rhs)
        );
    }
};

template <typename FoldingOperation, typename CombineOperation>
struct FoldAllElements<1, FoldingOperation, CombineOperation> {
    template <typename ResultT, typename T1, int D1, typename T2, int D2> 
    static ResultT fold(const Vector<T1, D1>& lhs, const Vector<T2, D2>& rhs) {
        return CombineOperation::perform<0, ResultT>(lhs, rhs);
    }
};

template <
    typename ScalarT,
    int Dim
>
class Vector {
public:
    Vector() {}
    ~Vector() {}

    explicit Vector(ScalarT v1, ScalarT v2 = 0, ScalarT v3 = 0, ScalarT v4 = 0,
 ScalarT v5 = 0, ScalarT v6 = 0, ScalarT v7 = 0, ScalarT v8 = 0, ScalarT v9 = 0,
 ScalarT v10 = 0) {
        CompileTimeAssert(Dim <= 10, Dimension_too_large_for_direct_init);
        init<Dim>(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10);
    }

    inline void LINALG_CALL_CONV nullify() {
        initop(*this, *this, NullOperation());
    }

    inline ScalarT LINALG_CALL_CONV elementSum() const {
        return FoldAllElements<Dim, SumFoldOperation, SelectLeftOperation>::fold<ScalarT>(*this, *this);
    }

    template <typename T2, int Dim2> 
    inline ScalarT LINALG_CALL_CONV operator%(const Vector<T2, Dim2>& rhs) const {
        return FoldAllElements<Dim, SumFoldOperation, ProductOperation>::fold<ScalarT>(*this, rhs);
    }

    template <typename T2, int Dim2>
    inline bool LINALG_CALL_CONV operator==(const Vector<T2, Dim2>& rhs) const {
        return FoldAllElements<Dim, AndFoldOperation, EqualsOperation>::fold<bool>(*this, rhs);
    }

    template <typename T2, int Dim2>
    inline bool LINALG_CALL_CONV operator!=(const Vector<T2, Dim2>& rhs) const {
        return FoldAllElements<Dim, OrFoldOperation, NotEqualsOperation>::fold<bool>(*this, rhs);
    }

    LINALG_VEC_BINARY_OPERATOR(operator+, SumOperation)
    LINALG_VEC_BINARY_OPERATOR(operator-, DifferenceOperation)
    LINALG_VEC_BINARY_OPERATOR(operator/, DivisionOperation)
    LINALG_VEC_BINARY_OPERATOR(operator*, ProductOperation)
    LINALG_VEC_BINARY_OPERATOR(operator^, CrossProductOperation)

    LINALG_VEC_UNARY_OPERATOR(operator+=, SumOperation)
    LINALG_VEC_UNARY_OPERATOR(operator-=, DifferenceOperation)
    LINALG_VEC_UNARY_OPERATOR(operator/=, DivisionOperation)
    LINALG_VEC_UNARY_OPERATOR(operator*=, ProductOperation)
    LINALG_VEC_UNARY_OPERATOR(operator^=, CrossProductOperation)

    inline ScalarT& LINALG_CALL_CONV operator[](unsigned int i) {
        return data_[i];
    }

    inline const ScalarT& LINALG_CALL_CONV operator[](unsigned int i) const {
        return data_[i];
    }

    template <int Index>
    inline void LINALG_CALL_CONV set(ScalarT value) {
        CompileTimeAssert(Index < Dim, Vector_Index_out_of_range);
        data_[Index] = value;
    }

    template <int Index>
    inline ScalarT LINALG_CALL_CONV get() const {
        CompileTimeAssert(Index < Dim, Vector_Index_out_of_range);
        return data_[Index];
    }

protected:
    template <typename T2, int Dim2, typename T3, int Dim3, typename Operation>
    inline explicit Vector(const Vector<T2, Dim2>& lhs, const Vector<T3, Dim3>& rhs, const Operation& tmp) {
        initop(lhs, rhs, tmp);
    }

    template <typename T2, int Dim2, typename T3, int Dim3, typename Operation>
    inline void LINALG_CALL_CONV initop(const Vector<T2, Dim2>& lhs, 
const Vector<T3, Dim3>& rhs, const Operation& tmp) {
        LoopOverAllElements<Dim, Operation>::loop(*this, lhs, rhs);
    }

    template <int N>
    inline void LINALG_CALL_CONV init(ScalarT v1, ScalarT v2, ScalarT v3,
 ScalarT v4, ScalarT v5, ScalarT v6, ScalarT v7, ScalarT v8, ScalarT v9, ScalarT v10) 
{
        data_[Dim-N] = v1;
        init<N-1>(v2, v3, v4, v5, v6, v7, v8, v9, v10, v1);
    }
    template <>
    inline void LINALG_CALL_CONV init<0>(ScalarT v1, ScalarT v2, ScalarT v3,
 ScalarT v4, ScalarT v5, ScalarT v6, ScalarT v7, ScalarT v8, ScalarT v9, ScalarT v10) {

    }

private:
    ScalarT data_[Dim];
};

} // namespace

#endif


Heb nu dit. Op zich wel mooie code, zo kan ik een 4d-double vector bv bij een 3d-float vector optellen oid. Tot 10d direct init, operators kan je heel simpel toevoegen. Zo is de equals-operator (and/equals fold) eigenlijk hetzelfde als de dotproduct (sum/product fold). Kon zelfs een kruis produkt als operator schrijven. Code is op zich snel, alleen wordt er dus idd geen parallele SSE gebruikt, dat is jammer. Komt zoiets uit:

GAS:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
; 28   :    f = v1 % (v2+v1);
    movss   xmm0, DWORD PTR _v1$[esp+24]
    movss   xmm5, DWORD PTR _v1$[esp+28]
    movss   xmm3, DWORD PTR _v1$[esp+32]
    movss   xmm1, DWORD PTR _v2$[esp+28]
    movaps  xmm2, xmm0
    addss   xmm2, DWORD PTR _v2$[esp+24]
    movaps  xmm4, xmm3
    addss   xmm4, DWORD PTR _v2$[esp+32]
    addss   xmm1, xmm5
    mulss   xmm0, xmm2
    mulss   xmm1, xmm5
    push    ecx
    addss   xmm0, xmm1
    mulss   xmm3, xmm4
    addss   xmm0, xmm3


Niet echt iets mis mee, maar misschien toch beter om gewoon een parallele vector te schrijven. Exceptions moeten hier alsnog uit overigens om alles te inlinen. Kan je dat voorkomen door aan te geven dat een bepaalde functie, of subs daarvan nooit exceptions gooien?

(maw, ik denk dat dit projectje weer ten einde is nu... Het was eventjes leuk :) )

[ Voor 7% gewijzigd door Zoijar op 11-10-2004 11:30 ]


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Ik zit op het moment wat ver weg van m'n normale machine (8000 km of zo) dus ik ga er nu even niet hier op in, maar als er interesse is wil ik 25/10 er wel over nadenken - ik zie de schop dan wel.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 17:05

.oisyn

Moderator Devschuur®

Demotivational Speaker

Overigens ook wel leuk Zoijar, dmv de , (komma) operator te overloaden kun je mooie initializer expressions maken

C++:
1
vector<float, 4> v = 1.f, 2.f, 3.f, 4.f;


Gewoon door steeds een reference te returnen naar de vector, maar dan als ander type met een int template parameter die aangeeft welk element gezet moet gaan worden

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <class T, int I> struct setter;

template <class T, int N> class vector
{
public:
    setter<vector, 1> & operator = (const T & t)
    {
        (*this)[0] = t;
        return reinterpret_cast<setter<vector, 1> &> (*this);
    }
    
    /* ... */
};

template <class T, int I> struct setter
{
    setter<T, I + 1> & operator , (const T & t)
    {
        reinterpret_cast<T &> (*this)[I] = t;
        return reinterpret_cast<setter<T, I + 1> &> (*this);
    }
};


You get the idea :)

(Overigens heb ik hier een gigantische vector en matrix template lib liggen waarbij je ook arbitrary swizzling kunt doen, bijvoorbeeld v[z,y,x,x] returnt een 4d vector met als eerste component de z-waarde van v, als 2e de y-waarde, etc.. Die x, y en z zijn dan variabelen van een bepaald template type, en door ze te combineren krijg je een gecombineerd type (de variabelen zelf hebben geen content, dat staat in de template parameters van de typen van de variabelen). En alle vectoren, setters, swizzled vectoren en expressies leiden weer terug naar een standaard basis vector type, zodat je elk van de soorten vectoren overal kunt gebruiken waar een vector verwacht wordt)

[ Voor 31% gewijzigd door .oisyn op 12-10-2004 09:44 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Ahh ok. Ik vroeg me wel al af hoe dat truukje ging, had het wel eens eerder gezien. Vermoedde wel dat het iets met de comma operator en een helper class was, maar het is nu helemaal duidelijk, thnx :)

Verder, tsja, ik weet dat er al kant en klare goeie/betere vector libs zijn. Bv. Blitz++. Ik had gewoon ineens zin om zoiets te maken, beetje een oefening ook. Hmmm, het is tegenwoordig lastig om iets nuttigs te maken, zo veel is er al, en beter...

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Helaas, maat je kunt operator,(int,int) niet overloaden. operator+(int,int) ook niet, om dezelfde reden: het zijn al built-ins.
Wat natuurlijk wel kan is
C++:
1
2
3
4
class ArgumentList { ... };
static ArgumentList args;
ArgumentList operator,(ArgumentList,int) { ... }
vector<int> foo = args,1,2,3,4;

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Je overload ook niet operator,(int, int), maar operator,(setter<T, I>, int). (((Vector = 1), 2), 3) wordt ((setter, 2),3) wordt (setter,3), omdat je operator= met een scalar een setter returned, en je overloaded operator, ook een setter.

(operator, heeft lagere precedence dan operator=...als enige)

[ Voor 14% gewijzigd door Zoijar op 15-10-2004 10:59 ]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 17:05

.oisyn

Moderator Devschuur®

Demotivational Speaker

precies, de , heeft een lagere precedence dan de =

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Hoewel ik die reinterpret cast erg lelijk vind. Zat te denken, je kan wel een const ref naar de vector in je setter meegeven...maar ik vraag me enigszins af of alles dan nog compiletime gaat. Ik denk dat Vector<float, 2> v = 1,2; std::cout << v[1]; dan niet meer iets wordt als push 2; call cout; Kan het eens proberen.

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
.oisyn schreef op 15 oktober 2004 @ 11:00:
precies, de , heeft een lagere precedence dan de =
In VC7.1 werkt het echter niet... is dat een compiler bug, of is er toch iets mis met de code? Het leek mij ook goed. De operator, van setter wordt helemaal nooit aangeroepen. Het lijkt erop dat vc de return value van operator= negeert, en dan de rest opvat als normale comma expressie (dus float,float)

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 17:05

.oisyn

Moderator Devschuur®

Demotivational Speaker

Dan heb je iets niet goed gedaan denk ik, ik heb het hier werkend op VC 7.1

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Oh stom, ik heb het al. Ik had wat veranderd, en later per ongeluk terug veranderd naar:
C++:
1
VectorSetter<ScalarT, Index+1> operator,(ScalarT& value) {...}

En uiteraard zijn die constante floats in de comma expressie 'const float', dus vandaar dat de overload niet kon...stom foutje.

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Heb het nu iets veranderd, vind het netter, een mooie compile time error bij een range error.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <typename ScalarT, int Dim, int Index>
class VectorSetter {
public:
    VectorSetter(Vector<ScalarT, Dim>& vec) : vec_(vec) {}

    VectorSetter<ScalarT, Dim, Index+1> operator , (const ScalarT& value) {
        vec_.set<Index>(value);
        return VectorSetter<ScalarT, Dim, Index+1>(vec_);
    }
private:
    Vector<ScalarT, Dim>& vec_;
};

VectorSetter<ScalarT, Dim, 1> operator=(const ScalarT& value) {
    (*this)[0] = value;
    return VectorSetter<ScalarT, Dim, 1>(*this);
}

Die kopieen worden mooi weg geoptimized. Dit gaat dus ook uiteindelijk als constanten.

code:
1
2
3
4
5
6
7
8
9
10
; 23   :    v1 = 1.0f, 2.0f, 3.0f;
; 24   :    cout << v1[0] << ", " << v1[1] << ", " << v1[2] << endl;

    push    1077936128              ; 40400000H
    push    OFFSET FLAT:$SG14758
    push    1073741824              ; 40000000H
    push    OFFSET FLAT:$SG14759
    push    1065353216              ; 3f800000H
    mov ecx, OFFSET FLAT:?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A
    call    ??6?$basic_ostream@DU?... etc


v1 = 1.0f, 2.0f, 3.0f, 4.0f; geeft nu mooi
"f:\programming\linalg\vector.h(170) : error C2087: 'ERROR_Vector_Index_out_of_range' : missing subscript"

[ Voor 27% gewijzigd door Zoijar op 15-10-2004 21:58 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Waarom krijg ik het niet voor elkaar om de operator* inline te krijgen? Het lukt alleen als ik C+ exceptions uitzet; maar ik heb wel elke functie gedeclareerd met "throw()", dus waarom ziet de compiler niet dat die geen exceptions gooien?

Vector<T, N> operator*(const Vector<T,N>& rhs) const throw() { ... }

En ook:

Vector<T, N> __declspec(nothrow) operator*(const Vector<T,N>& rhs) const { ... }

werkt niet...alle aangeroepen functies hebben ook een throw() erachter staan...

(edit)
Ik heb het al. Blijkbaar als je een dtor defineert in je class dan gaat het niet meer. Ook al is deze leeg. Beetje vreemd, wat is de rede hiervoor, weet iemand dat? Ik had mijn dtor zelfs zo: ~Vector() throw() {}, compiler moet dan toch wel zien dat die aanroep niet nodig is?

[ Voor 27% gewijzigd door Zoijar op 16-10-2004 13:38 ]


  • SWfreak
  • Registratie: Juni 2001
  • Niet online
.oisyn schreef op 12 oktober 2004 @ 09:37:
(Overigens heb ik hier een gigantische vector en matrix template lib liggen waarbij je ook arbitrary swizzling kunt doen, bijvoorbeeld v[z,y,x,x] returnt een 4d vector met als eerste component de z-waarde van v, als 2e de y-waarde, etc.. Die x, y en z zijn dan variabelen van een bepaald template type, en door ze te combineren krijg je een gecombineerd type (de variabelen zelf hebben geen content, dat staat in de template parameters van de typen van de variabelen). En alle vectoren, setters, swizzled vectoren en expressies leiden weer terug naar een standaard basis vector type, zodat je elk van de soorten vectoren overal kunt gebruiken waar een vector verwacht wordt)
Ik ben die swizzle-operator eens gaan uitwerken en dat is op zich best wel simpel.
(Ingekorte versie)
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//forward declaration
template<int type, int i1>
class mVectorGetter1;

//hier horen nog mVectorGetter3 en 4

template<int type, int i1, int i2>
class mVectorGetter2
{
//hier hoort ook een operator,
};

template<int type, int i1>
class mVectorGetter1
{
public:
    template<int i2>
    mVectorGetter2<type,i1,i2> &operator,(mVectorGetter1<type,i2> &other) {
        return reinterpret_cast<mVectorGetter2<type,i1,i2> &> (*this);
    };
};

mVectorGetter1<0,0> x;
mVectorGetter1<0,1> y;
mVectorGetter1<0,2> z;
mVectorGetter1<0,3> w;
mVectorGetter1<1,0> s;
mVectorGetter1<1,1> t;
mVectorGetter1<1,2> p;
mVectorGetter1<1,3> q;

//in vector class
template<int type, int i1>
mVector<T,1> &operator[](const mVectorGetter1<type,i1> &ind) {
    return *(new mVector<T,1>( get<i1>() ));
};
//en vergelijkbaar voor de andere 3

//bij gebruik
mVector<float, 4> v = 1.0f, 1.0f, 1.0f, 1.0f;
mVector<float, 4> w = v[x,z,y];

v[x,z] = 2.0f, 2.0f;    //2: geeft niet verwachte resultaat
int x = 10;
float f = v[x];          //3: geeft niet verwachte resultaat

(type in mVectorGetter is om te zorgen dat je niet xyzw en stpq met elkaar gaat mixen)

Een paar problemen met deze code:
1. Als je in een functie int x = 10; declareert, dan hide je daarmee de x van je swizzle. Een vector[x] geeft dan niet het verwachte resultaat.
2. Je wilt graag expressies van het type vector[x,z] = 1.0f, 1.0f; kunnen opschrijven. Verwacht is dan dat x en z coordinaat van vector veranderen. Met de huidige code gebeurt dat echter niet. Er is gewoon een kopie van vector gemaakt en daarin worden dan dingen gewijzigd, ipv in vector zelf.
3. Er zit erg veel herhaling in de code. Het scheelt dat men over het algemeen indexeert met xyzw of stpq, maar abcdefghijklmn zou echt een rommel worden (te veel getter-classes).

Probleem 1 kun je oplossen door geen operator[](int) te implementeren, maar dat helpt vind ik het idee van een vector om zeep.
Probleem 2 is wel op te lossen als je maar 1 element opvraagt (return reinterpret_cast<mVector<T,1> &> (*(element + i1)); oid), maar das geen complete oplossing.
Probleem 3 :?

Zijn er ideeen hoe dit op te lossen?

[ Voor 5% gewijzigd door SWfreak op 17-10-2004 22:32 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Ik zal morgen nog eens kijken... maar even tussendoor, mijn code zuigt behoorlijk. Toch maar over op expression templates, om uiteindelijk op blitz uit te komen...maarja, het gaat erom weer wat kennis op te doen.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 17:05

.oisyn

Moderator Devschuur®

Demotivational Speaker

SWFreak: dit is dan ook enorm lelijk, optimaliseert voor geen meter en geeft bovendien memory leaks
C++:
32
33
34
35
36
//in vector class 
template<int type, int i1> 
mVector<T,1> &operator[](const mVectorGetter1<type,i1> &ind) { 
    return *(new mVector<T,1>( get<i1>() )); 
}; 


Daarnaast moet je voor elke dimensie een nieuwe set swizzlers gaan maken. Handiger is om gewoon een templated lijst van integers te maken, dan kun je net zo ver als je zelf wilt, en ook die new moet je wegmieteren...

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • SWfreak
  • Registratie: Juni 2001
  • Niet online
Ik ben het natuurlijk met je kritiek eens (had t probleem ook al gesignaleerd). Maar wat bedoel je met een templated lijst van integers?
Ik zou me zoiets kunnen voorstellen:
C++:
1
2
3
4
5
6
7
8
9
10
11
template<int size, int index, typename T>
class mVectorGetter
{
    template<int index2, typename U>
    const mVectorGetter<size+1, index, mVectorGetter<size, index2, T> >
          &operator,(const mVectorGetter<1,index2,U> &other)
    {
        return reinterpret_cast<mVectorGetter<size+1, index, 
                mVectorGetter<size+1, index2, T> > >(*this);
    };
};

Weet niet of het werkt (geen compiler hier). size maakt het later makkelijk om de grootte van de resultaat vector te bepalen. operator[] in mVector heb ik niet uitgewerkt, maar dat is wel te doen als bovenstaande werkt.

Die new wil ik graag wegmieteren, maar zonder new gaat de compiler klagen en een expressie als vector[x,z] wordt dan ook lastig. m.a.w. zou iets meer over je suggestie kunnen vertellen?

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 17:05

.oisyn

Moderator Devschuur®

Demotivational Speaker

De templated lijst van integers kun je simpel implementeren: een template integer parameter voor de huidige integer, en vervolgens een template type parameter voor het volgende type (wat weer een combinatie integer-type is, waarvan de type ook weer zo'n combinatie is, etc.)

Voor de swizzlers krijg je dan zoiets:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template <int I, class NEXT> struct Swizzler
{
    typedef NEXT next;
    enum { index = I, count = next::count + 1 };
    static int getindex (int i) { return i == count ? index : next::getindex (i); }
};

template <int I> struct Swizzler<I, void>
{
    typedef void next;
    enum { index = I, count = 0 };
    static int getindex (int i) { return i == count ? index : -1; }
};

template <int I1, class SUB1, int I2>
Swizzler<I2, Swizzler<I1, SUB1> > operator , (Swizzler<I1, SUB1>, Swizzler<I2, void>)
{
    return Swizzler<I2, Swizzler<I1, SUB1> > ();
}


Als je een vector dan met een swizzler indexeert, dan geef je een referentie naar een ander type vector terug met als adres de originele vector, en die andere type heeft dan een aangepaste operator [] waarin je een lookup doet adhv de swizzler

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template <class T, class S> struct Getter
{
    typedef typename T::value_type value_type;

    value_type & operator [] (int index)
    {
        return reinterpret_cast<T &> (*this)[S::getindex (i)]; }
    }

    const value_type & operator [] (int index) const
    {
        return reinterpret_cast<const T &> (*this)[S::getindex (i)]; }
    }
};

template </* ... */> struct Vector
{
    // ...

    template <int I, class N>
    Getter<Vector, Swizzler<int I, class N> > & operator [] (Swizzler<I, N>)
    {
        return reinterpret_cast<Getter<Vector, Swizzler<int I, class N> > &> (*this);
    }
};


Die getter heeft als template-argument het onderliggende type, zodat je een soort van compile-time polymorfisme toe kunt passen.

Op die manier retourneert iets als v[z,y,x] een vector type die je weer kunt indexeren, v[z,y,x][0] geeft dan ook de z-component terug.

Als je het slim doet implementeer je die standaard operator[] en operator= behaviour in een basisvector die een template type parameter naar het onderliggende type heeft. Vervolgens laat je al je verschillende vector types (dus ook expressies e.d.) van deze basisvector inheritten. Ook is het handig om dan een integer template parameter toe te voegen dat het aantal elementen weergeeft, zodat je generieke functies kunt maken die een vector van een bepaald aantal elementen accepteert. Op die manier kun je elke soort vector aan die functie geven.

[ Voor 17% gewijzigd door .oisyn op 18-10-2004 14:10 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 16:18
Ik moet zeggen dat ik erg onder de indruk ben van deze code, maar ik vraag me af of zoiets nou in de praktijk gebruikt wordt ? Is het nog door normaal persoon te debuggen? :)

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 17:05

.oisyn

Moderator Devschuur®

Demotivational Speaker

Nee, en ik krijg in debug builds zelfs warnings dat de symbols te lang worden :+

[ Voor 12% gewijzigd door .oisyn op 18-10-2004 15:14 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Zoijar schreef op 16 oktober 2004 @ 13:31:
Waarom krijg ik het niet voor elkaar om de operator* inline te krijgen? Het lukt alleen als ik C+ exceptions uitzet; maar ik heb wel elke functie gedeclareerd met "throw()", dus waarom ziet de compiler niet dat die geen exceptions gooien?
Omdat throw() niet betekent dat een functie geen exceptions gooit. Het gevolg is alleen dat exceptions tot een exit leiden, zonder een verdere stack unwind.
Dat betekent in de praktijk dat de compiler voor een throw() een jump moet introduceren.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 16:18
.oisyn schreef op 18 oktober 2004 @ 15:14:
Nee, en ik krijg in debug builds zelfs warnings dat de symbols te lang worden :+
Dat vermoeden had ik al :)

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
MSalters schreef op 18 oktober 2004 @ 17:34:
Omdat throw() niet betekent dat een functie geen exceptions gooit. Het gevolg is alleen dat exceptions tot een exit leiden, zonder een verdere stack unwind.
Dat betekent in de praktijk dat de compiler voor een throw() een jump moet introduceren.
Ja ok, het ging ook vooral om het verschil tussen een lege dtor en geen dtor. Is dat gewoon een optimizer "bug", of zit er een diepere gedachte achter? Ik denk dat de compiler gewoon een flag zet "needs-dtor-call", ook al is die dtor leeg. En dat er dan niet inlined wordt.


Andere vraag, is het mogelijk om automatic template deduction te doen op return types? Of een andere manier om dat zelfde te bereiken? Dus zoiets:

C++:
1
2
3
4
template <typename ResultT, typename T1, typename T2>
ResultT foo(T1 x, T2 y) { return x + y;}

std::cout << foo(1.0f, 2.0);


Het punt is hier als bv T1=float en T2=double, dan is het resultaat van de operator+ een double. Je kan dus niet bv gewoon T1 returnen. Je zou wel met type hierarchy kunnen kijken welk type in welk omzetbaar is, zoals met float/double...maar met userdefined classes gaat het dan toch lastiger worden lijkt me. Zeg bv het type van T1+T2 is dan een heel ander type T3.
Is er wel iets soort gelijks te doen?

dus om dit stukje uit de standaard wil ik heen:
(Note: if a template-parameter is only used to repre-
sent a function template return type, its corresponding template-argu-
ment cannot be deduced and the template-argument must be explicitly
specified. )

[ Voor 15% gewijzigd door Zoijar op 19-10-2004 00:07 ]


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Zoijar schreef op 18 oktober 2004 @ 23:50:
[...]

Ja ok, het ging ook vooral om het verschil tussen een lege dtor en geen dtor. Is dat gewoon een optimizer "bug", of zit er een diepere gedachte achter? Ik denk dat de compiler gewoon een flag zet "needs-dtor-call", ook al is die dtor leeg. En dat er dan niet inlined wordt.
Wat heeft een dtor met een member "T operator* throw()" te maken?

Het probleem met een throw() is dat je er code voor moet genereren. Deze code heeft altijd een branch, en dat breekt de optimizer. Het kwam toevallig net op tijdens de ISO meeting, en de Sun afgevaardigde gaf aan dat dat precies het probleem is.
Andere vraag, is het mogelijk om automatic template deduction te doen op return types? Of een andere manier om dat zelfde te bereiken? Dus zoiets:
C++:
1
2
3
4
template <typename ResultT, typename T1, typename T2>
ResultT foo(T1 x, T2 y) { return x + y;}

std::cout << foo(1.0f, 2.0);


Het punt is hier als bv T1=float en T2=double, dan is het resultaat van de operator+ een double. Je kan dus niet bv gewoon T1 returnen. Je zou wel met type hierarchy kunnen kijken welk type in welk omzetbaar is, zoals met float/double...maar met userdefined classes gaat het dan toch lastiger worden lijkt me. Zeg bv het type van T1+T2 is dan een heel ander type T3.
Is er wel iets soort gelijks te doen?
Dit is de standaard case voor typeof/decltype. Je moet preciezer zijn, anders is het antwoord "ja" zonder verdere toelichting.

Een simpele oplossing is een resultof_plus<T1,T2>:type template. Elk user-defined type zou dan specialisaties moeten definieren, maar dat gaat niet als je een user-defined template gebruikt.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
MSalters schreef op 19 oktober 2004 @ 01:20:
Wat heeft een dtor met een member "T operator* throw()" te maken?

Het probleem met een throw() is dat je er code voor moet genereren. Deze code heeft altijd een branch, en dat breekt de optimizer. Het kwam toevallig net op tijdens de ISO meeting, en de Sun afgevaardigde gaf aan dat dat precies het probleem is.
Ik was in de war, vanwege het feit dat ik twee dingen tegelijk veranderde. Die throw() had er idd niets mee te maken zie ik nu, sorry. Ik was onder de indruk dat een throw() er voor zorgde dat er geen stack unwinding nodig is. Maar dat is natuurlijk niet zo, want er kan nog steeds een exception voorkomen, alleen wordt er dan unexpected aangeroepen. Maar dan is er nog steeds stack unwinding toch? De link met een dtor was dat als ik een lege dtor gebruik, de compiler wel unwinding code maakt, maar met een default dtor niet en dus inlined.
Dit is de standaard case voor typeof/decltype. Je moet preciezer zijn, anders is het antwoord "ja" zonder verdere toelichting.

Een simpele oplossing is een resultof_plus<T1,T2>:type template. Elk user-defined type zou dan specialisaties moeten definieren, maar dat gaat niet als je een user-defined template gebruikt.
Ja dat bedoelde ik, met typeof::before. Dat resultof_plus heb ik ook aan gedacht, maar dan zou je veel combinaties krijgen...niet echt wenselijk. Maar iets als 'typeof(T1+T2)' bestaat dus niet? Was dat nou een voorstel voor C++0x?

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Zoijar schreef op 19 oktober 2004 @ 09:53:
[...]

Ik was in de war, vanwege het feit dat ik twee dingen tegelijk veranderde. Die throw() had er idd niets mee te maken zie ik nu, sorry. Ik was onder de indruk dat een throw() er voor zorgde dat er geen stack unwinding nodig is. Maar dat is natuurlijk niet zo, want er kan nog steeds een exception voorkomen, alleen wordt er dan unexpected aangeroepen. Maar dan is er nog steeds stack unwinding toch? De link met een dtor was dat als ik een lege dtor gebruik, de compiler wel unwinding code maakt, maar met een default dtor niet en dus inlined.
Stack unwinding voorafgaand aan terminate() is niet toegestaan als de terminate het gevolg is van een te krappe exception-specifiction (15.5.1, een na laatste bullet)
[...]
Ja dat bedoelde ik, met typeof::before. Dat resultof_plus heb ik ook aan gedacht, maar dan zou je veel combinaties krijgen...niet echt wenselijk. Maar iets als 'typeof(T1+T2)' bestaat dus niet? Was dat nou een voorstel voor C++0x?
Ja, alhoewel de huidige naam decltype is (type of declaration, dus declaration van de relevante operator+ in dit geval). Voorstel N1705, om precies te zijn.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Ik had nu zoiets...
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
template <bool expr, typename T1, typename T2>
struct IfThenElse {
    typedef T1 Result;
};

template<typename T1, typename T2>
struct IfThenElse<false, T1, T2> {
    typedef T2 Result;
};

template <typename T1, typename T2>
class ReturnOf {
    typedef char NoneSize;
    class T1Size {char a[2];};
    class T2Size {char a[65];};

    static T1 MakeT1();
    static T2 MakeT2();
    static T1Size Test(T1);
    static T2Size Test(T2);
    static NoneSize Test(...);

public:
    typedef typename 
    CT_TMP::IfThenElse<
        sizeof(Test(MakeT1()+ MakeT2())) == sizeof(T1Size),
        T1, 
        typename CT_TMP::IfThenElse<
        sizeof(Test(MakeT1()+ MakeT2())) == sizeof(T2Size),
            T2,
            void
        >::Result
    >::Result Type;
};

... en een spec als de types gelijk zijn zodat de twee Test() functies niet dezelfde signature zouden hebben.

int main() {
   std::cout << typeid(ReturnOf<double, float>::Type).name() << std::endl; // double
   std::cout << typeid(ReturnOf<float, double>::Type).name() << std::endl; // double
   std::cout << typeid(ReturnOf<int, float>::Type).name() << std::endl; // float
   std::cout << typeid(ReturnOf<int, int>::Type).name() << std::endl; // int


Maar dat is omslachtig en niet echt bruikbaar. Ik kan namelijk van die operator+ in die class geen template parameter maken, want dan zou ik weer het return type moeten opgeven van de aan te roepen functie (Zelfde met een functie pointer. Of elk ander mechanisme?)
Ook iets dat een heel ander type returned dan de twee inputs wordt nu void. Maar op zich is dit misschien wel een goeie default, en dat je bepaalde cases dan kan specializen.
Pagina: 1