[ C++ ] impliciete conversie van built-in types nabootsen

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
Ik was aan het testen met templates en liep tegen het volgende probleem aan.

Als je een float en een integer optelt en in een float opslaat, dan wordt de floating point waarde behouden. Als je echter templates maakt die min of meer hetzelfde gedrag als de built-in types vertonen, dan is deze floating point waarde niet behouden.

Ik vroeg me af of er een manier is om dit built-in gedrag te kopieren naar custom classes.

Hier een compileerbaar voorbeeld:
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
#include <iostream>

template< class T >
class Custom {

    public:
        Custom( const T &t ) :
                data( t ) {}

        template< typename U >
        operator Custom< U >() {
            return Custom< U >( this->value() );
        }
        
        const T& value() const {
            return data;
        }

    private:
        T data;
};

template< typename T, typename U >
Custom< T > operator+( const Custom< T > &A, const Custom< U > &B ) {
    return Custom< T >( A.value() + B.value() );
}

using namespace std;

int main( int, char** ) {

    Custom<int> A( 1 );
    Custom<float> B( 0.1 );

    Custom<float> C = A + B;

    Custom<float> D = static_cast< Custom<float> >( A ) + B;

    int a = 1;
    float b = 0.1;
    float c = a + b;

    std::cout << "standard: " << c << std::endl;
    std::cout << "custom: " << C.value() << std::endl;
    std::cout << "custom with explicit cast: " << D.value() << std::endl;

    return 0;
}


Als ik deze code lees, dan snap ik wel waarom "Custom<float> C = A + B;" als integer berekend word, ik vraag me alleen af waarom de built-in types hier wel mee om kunnen gaan.

Als iemand hier een licht op kan schijnen hoor ik het graag :)

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • MLM
  • Registratie: Juli 2004
  • Laatst online: 12-03-2023

MLM

aka Zolo

In het eerste geval instantieer je de operator + functie met T=int, U=float. Je returned daarna Custom<T>, dus, een int :)
Daarna, converteer je impliciet van Custom<int> naar Custom<float> (immers, je assigned naar Custom<float>).

Als jij dat als float wilt berekenen, moet je of je eerste argument float maken (b + a gaat wel werken :P), of je moet expliciet template arguments definieren, maar dat gaat volgens mij niet met operators zonder dat het er fugly uit gaat zien :)

-niks-


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
MLM schreef op dinsdag 04 augustus 2009 @ 16:40:
In het eerste geval instantieer je de operator + functie met T=int, U=float. Je returned daarna Custom<T>, dus, een int :)
Daarna, converteer je impliciet van Custom<int> naar Custom<float> (immers, je assigned naar Custom<float>).
Arjan schreef op dinsdag 04 augustus 2009 @ 16:13:
[...]

Als ik deze code lees, dan snap ik wel waarom "Custom<float> C = A + B;" als integer berekend word, ik vraag me alleen af waarom de built-in types hier wel mee om kunnen gaan.
[...]
;)
Als jij dat als float wilt berekenen, moet je of je eerste argument float maken (b + a gaat wel werken :P), of je moet expliciet template arguments definieren, maar dat gaat volgens mij niet met operators zonder dat het er fugly uit gaat zien :)
Dat zou inderdaad werken, ik ben dit echter aan het schrijven als onderdeel van een library en wil mijn gebruikers objecten voorschotelen die zo goed als mogelijk werken zoals de built-in types.

Omdat ik niet weet waarom de built-in types deze conversie kunnen doen, weet ik ook niet hoe ik dit gedrag zou moeten nabouwen, áls het überhaupt al kan :)

Als er geen mogelijkheid voor bestaat, dan zal ik inderdaad zoals je al aangaf custom template conversies ofzo moeten schrijven, maar dat doe ik liever niet.. dus mijn vraag blijft bestaan

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Als ik me niet vergis is het resultaat van 2 operaties altijd dat met de grootste precisie. De reden ligt trouwens niet bij de operator maar bij het impliciet casten.

Bovendien denk ik dat je, om de C++ regels beter te benaderen (en eigenlijk ook gebruiken) volgende code nodig hebt:
C++:
1
2
3
4
template< typename T >
Custom< T > operator+( const Custom< T > &A, const Custom< T > &B ) {
    return Custom< T >( A.value() + B.value() );
} 

Daarbij cast je je types naar 1 type voor je de operatie doet.
Hierbij zal een een int naar een float geconverteerd worden ipv een float naar een int.

[ Voor 6% gewijzigd door H!GHGuY op 04-08-2009 16:59 ]

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • coubertin119
  • Registratie: Augustus 2002
  • Laatst online: 15-09 17:06
Built-in types en uitdrukkingen waar deze in voorkomen worden via een systeem van promoties en conversies geïnterpreteerd. Er wordt gezocht naar een manier om de uitdrukking te parsen, waarbij bepaalde regels in acht worden genomen (zie de bijbel van Stroustrup, Appendix C.6) en die regels zitten in de compiler en voldoen niet aan het gedrag dat je nu met templates probeert te bereiken :).

Als je het gedrag wil nabouwen zal je in elk geval al je returntype niet mogen laten afhangen van het type van je linkerlid :P. Misschien dat wat spelen met conversieoperatoren je bij het gedrag van de compiler krijgt, maar dat komt dan enkel omdat je net die conversieoperatoren initialiseert die de compiler verwacht om die expressies te kunnen parsen volgens zijn eigen regels. Vermoed ik, want hier begeef ik me op glad ijs :).


Of wat H!GHGuY zegt, maar dan zonder conversieoperatoren. Al is het dus wel valsspelen, gedrag van de compiler in templates proberen te bevatten door het gedrag door die compiler te laten bepalen.

[ Voor 10% gewijzigd door coubertin119 op 04-08-2009 17:03 ]

Skat! Skat! Skat!


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

coubertin119 schreef op dinsdag 04 augustus 2009 @ 17:00:
Of wat H!GHGuY zegt, maar dan zonder conversieoperatoren. Al is het dus wel valsspelen, gedrag van de compiler in templates proberen te bevatten door het gedrag door die compiler te laten bepalen.
De compiler volgt (mag ik hopen) de C++ standaard en die legt dan wel vast wat er gebeurt. Bovendien gaf de TS aan de compiler te willen volgen, dus ik zie geen graten?

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • coubertin119
  • Registratie: Augustus 2002
  • Laatst online: 15-09 17:06
H!GHGuY schreef op dinsdag 04 augustus 2009 @ 17:05:
[...]


De compiler volgt (mag ik hopen) de C++ standaard en die legt dan wel vast wat er gebeurt. Bovendien gaf de TS aan de compiler te willen volgen, dus ik zie geen graten?
Er is niks mis mee, tenzij je het systeem van conversies en promoties zélf wil vastleggen. Het is maar net wat je wil natuurlijk, de compiler laten beslissen of zelf beslissen :).

Skat! Skat! Skat!


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
coubertin119 schreef op dinsdag 04 augustus 2009 @ 17:10:
[...]

Er is niks mis mee, tenzij je het systeem van conversies en promoties zélf wil vastleggen. Het is maar net wat je wil natuurlijk, de compiler laten beslissen of zelf beslissen :).
Ik wil gezien de situatie de compiler laten beslissen, en dan moet ik dus zoals H!GHGuY schreef de +operator opnieuw defineren.
H!GHGuY schreef op dinsdag 04 augustus 2009 @ 16:58:
Als ik me niet vergis is het resultaat van 2 operaties altijd dat met de grootste precisie. De reden ligt trouwens niet bij de operator maar bij het impliciet casten.

Bovendien denk ik dat je, om de C++ regels beter te benaderen (en eigenlijk ook gebruiken) volgende code nodig hebt:
C++:
1
2
3
4
template< typename T >
Custom< T > operator+( const Custom< T > &A, const Custom< T > &B ) {
    return Custom< T >( A.value() + B.value() );
} 

Daarbij cast je je types naar 1 type voor je de operatie doet.
Hierbij zal een een int naar een float geconverteerd worden ipv een float naar een int.
Dit vereist echter dat Custom<A> impliciet naar Custom<B> geconverteerd kan worden. Dit lukt nog niet, ik heb het volgende geprobeerd:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template< class T >
class Custom {

    public:
        Custom( const T &t ) :
                data( t ) {}

        template< typename U >
        Custom( const Custom< U > &t ) :
                data( t.value() ) {}
        
        const T& value() const {
            return data;
        }

    private:
        T data;
};

template< typename T >
const Custom< T > operator+( const Custom< T > &A, const Custom< T > &B ) {
    return A.value() + B.value();
}

De compiler ziet dit echter niet zitten, en weet mij het volgende te melden:
code:
1
error: no match for operator+ in A + B

Kennelijk ziet ie dus niet dat A en/of B omgezet moet worden naar de ander z'n type zodat deze conversie wel kan..

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • coubertin119
  • Registratie: Augustus 2002
  • Laatst online: 15-09 17:06
Wat krijg je als je een conversieoperator definieert? Want nu zijn het twee aparte types die je probeert op te tellen, en je huidige operator+ werkt op twee operanden van hetzelfde type (eventueel na impliciete conversie, maar 2 instanties van een klassetemplate met een andere templateparameter zijn volstrekt andere types die op geen enkele manier met elkaar in relatie staan). Je vorige code (met een linkerlid van type T en rechterlid van type U) werkte omdat je functie bestond uit een optelling van float en int en die was geldig voor die combinatie van templateparameters.

Skat! Skat! Skat!


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
coubertin119 schreef op dinsdag 04 augustus 2009 @ 17:46:
Wat krijg je als je een conversieoperator definieert?
[...]
Die zit er al in ;)
C++:
1
2
3
        template< typename U >
        Custom( const Custom< U > &t ) :
                data( t.value() ) {} 


Ik had de eerste versie:
C++:
1
2
3
4
        template< typename U >
        operator Custom< U >() {
            return Custom< U >( this->value() );
        } 

al vervangen met deze nieuwe omdat ik dacht dat deze laatste misschien geen impliciete conversies ondersteund, maar volgens mij zou dit niet uit mogen maken en mijn tests geven inderdaad een vergelijkbaar gedrag, maar ze worden nog steeds niet impliciet geconverteerd.

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

right...

Hier zou de "alternate function syntax" van C++0x wel kunnen helpen denk ik:
Wikipedia: C++0x

Ik weet niet of er een "workaround' is voor de huidige C++.

[ Voor 24% gewijzigd door H!GHGuY op 04-08-2009 19:01 ]

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Arjan schreef op dinsdag 04 augustus 2009 @ 16:13:
Als ik deze code lees, dan snap ik wel waarom "Custom<float> C = A + B;" als integer berekend word, ik vraag me alleen af waarom de built-in types hier wel mee om kunnen gaan.
De built-in types kunnen "ermee omgaan" omdat er speciale promotie en conversieregels bestaan voor die built-in types. Als je dat wilt nabootsen dan zul je dat moeten definieren, of gewoon gebruik moeten maken van C++0x's decltype zoals H!GHGuY al zei.

In C++03 is het nogal monnikenwerk:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
template<class T> struct identity { typedef T type; };

template<class T, class U> struct mathresult;
template<class T> struct mathresult<T, T> : identity<T> { };
template<> struct mathresult<int, float> : identity<float> { };
template<> struct mathresult<float, int> : identity<float> { };
// en verder alle (relevante) combinaties van built-in types.

// use case:
template<class T, class U>
Custom<typename mathresult<T,U>::type>
    operator+(const Custom<T> &, const Custom<U> &)
{ /* ... */ }

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.


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
Ok, helder :)

Orginele oplossing .oisyn, hoewel het volgens mij een overkill is voor deze situatie :p

De volgende code voorziet voor zover ik kan zien in het oplossen van dit 'probleem'.
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
#include <iostream> 

template< class T >
class Custom {

    public:
        Custom( const T &t ) :
                data( t ) {}
        
        const T& value() const {
            return data;
        }

    private:
        T data;
};

template< typename T, typename U >
const Custom< T > operator+( const Custom< T > &A, const Custom< U > &B ) {
    return A.value() + B.value();
}

template< typename T >
const Custom< float > operator+( const Custom< T > &A, const Custom< float > &B ) {
    return A.value() + B.value();
}

int main( int, char** ) {

    Custom<int> A( 1 );
    Custom<float> B( 0.1 );
    int a = 1;
    float b = 0.1;

    float c = a + b;

    float d = b + a;

    Custom<float> C = A + B;

    Custom<float> D = B + A;

    std::cout << "standard: " << c << std::endl;
    std::cout << "standard flipped: " << d << std::endl;
    std::cout << "custom: " << C.value() << std::endl;
    std::cout << "custom flipped: " << D.value() << std::endl;

    return 0;
}

In de praktijk zou je ook de template voor double moeten defineren, maar het idee is duidelijk.

Wat ik me nog afvraag, waarom probeert de compiler de vorige oplossing niet op te lossen door het Custom object te converteren?

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • coubertin119
  • Registratie: Augustus 2002
  • Laatst online: 15-09 17:06
Omdat hij niet weet hoe hij een instantie van Costum<int> kan omvormen naar Costum<float> of omgekeerd. Het zijn klassen van een verschillend type (dat ze toevallig uit dezelfde template geïnstantieerd worden boeit niet). Je conversieoperator zal trouwens, ook al zou de impliciete conversie lukken, de conversie in beide richtingen doen waardoor hij niet weet welke hij moet kiezen.

Wild idee: behoud je conversieoperator/constructor en geef specializations op gebaseerd op de (partiële) ordening gedefinieerd volgens die promotie- en conversieregels voor die built-in types.

Skat! Skat! Skat!


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

De compiler neemt jouw operator niet in beschouwing aangezien hij een operator zoekt voor
C++:
1
?? operator+(Custom<int>&, Custom<float>&)

Vermoedelijk neemt de compiler hierbij geen getemplate conversies in beschouwing, als hij al impliciete conversies in beschouwing neemt.
.oisyn, als C++ kenner zal hier wel verder over kunnen uitbreiden.

@TS

Denk eens wat jouw code doet bij
C++:
1
2
3
4
Custom<double> a(0.9);
Custom<float> b(0.7f);

Custom<double> c = a + b;

[ Voor 16% gewijzigd door H!GHGuY op 04-08-2009 21:59 ]

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • xos
  • Registratie: Januari 2002
  • Laatst online: 12-09 12:41

xos

Volgens mij wil je echt het gedrag nabouwen dan ontkom je niet aan een het definieren van de conversie rules. De oplossing die .oisyn aandraagt is dan imho geen overkill maar noodzakelijk.

Uit intresse heb ik ook eens een implementatie gemaakt. Gebruik van templates is min of meer verboden door onze architect zonder duidelijke reden... Af en toe prutsen is dan wel leuk :)

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
#include <cstdlib>
#include <iostream>

// forward decl.
template<class L, class Op, class R> struct expression;

template<class T>
struct my_type 
{
    my_type(const T& v) { value = v; }

    template<class L, class Op, class R>
    my_type(const expression<L, Op, R>& source)
    { value = source(); }

    T operator()() const { return value; }
    T value;
};

struct plus
{
    template<class T1, class T2>
    static double apply(const T1& l, const T2& r) 
    { return l() + r(); }
};

struct minus
{
    template<class T1, class T2>
    static double apply(const T1& l, const T2& r)
    { return l() - r(); }
};

template<class L, class Op, class R>
struct expression
{
    expression(const L& l, const R& r) : l(l), r(r) {}

    double operator()() const
    { return Op::apply(l,r); }
    
    const L& l;
    const R& r;
};

template<class L, class R>
expression<L,plus,R> operator+(const L& l, const R& r)
{ return expression<L,plus,R>(l, r); }

template<class L, class R>
expression<L,minus,R> operator-(const L& l, const R& r)
{ return expression<L,minus,R>(l, r); }

int main(int argc, char *argv[], char *env[])
{
    my_type<int> i1 = 1;
    my_type<int> i2 = 2;
    my_type<float> f1 = 3.5f;
    my_type<float> f2 = 0.3f;

    my_type<int> result1 = i1 - f1 + i2 + f2;
    my_type<float> result2 = i1 - f1 + i2 + f2;

    std::cout << result1() << std::endl;
    std::cout << result2() << std::endl;

    return EXIT_SUCCESS;    
}

Ik ben wel benieuwd hoe je de bewerkingsvolgorde wilt gaan respecteren, of beperk je je alleen tot optellen en aftrekken?

Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
coubertin119 schreef op dinsdag 04 augustus 2009 @ 21:31:
Omdat hij niet weet hoe hij een instantie van Costum<int> kan omvormen naar Costum<float> of omgekeerd. Het zijn klassen van een verschillend type (dat ze toevallig uit dezelfde template geïnstantieerd worden boeit niet). Je conversieoperator zal trouwens, ook al zou de impliciete conversie lukken, de conversie in beide richtingen doen waardoor hij niet weet welke hij moet kiezen.
Klinkt logisch, zoals H!GHGuY ook uitlegt in zijn post.
Wild idee: behoud je conversieoperator/constructor en geef specializations op gebaseerd op de (partiële) ordening gedefinieerd volgens die promotie- en conversieregels voor die built-in types.
Dit volg ik even niet.. care to elaborate?
H!GHGuY schreef op dinsdag 04 augustus 2009 @ 21:57:
[...]

Denk eens wat jouw code doet bij
C++:
1
2
3
4
Custom<double> a(0.9);
Custom<float> b(0.7f);

Custom<double> c = a + b;
goed punt, redelijk simpel op te lossen met de volgende regels, al zou je dit voor de volledigheid voor alle built-in types moeten doen natuurlijk.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
template< typename T >
const Custom< float > operator+( const Custom< T > &A, const Custom< float > &B ) {
    return A.value() + B.value();
}

template< typename T >
const Custom< double > operator+( const Custom< T > &A, const Custom< double > &B ) {
    return A.value() + B.value();
}

const Custom< double > operator+( const Custom< double > &A, const Custom< float > &B ) {
    return A.value() + B.value();
}


/edit

deze methode werkt opzich wel, maar wordt wel erg extensive als je alle built-in operators erin wil verwerken... Ik ga de oplossing van .oisyn en xos nog eens even bestuderen

[ Voor 5% gewijzigd door Arjan op 04-08-2009 23:25 ]

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
als het alleen maar correct hoeft is dit wellicht een oplossing
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
#include <iostream> 

template< class T >
class Custom {

    public:
        Custom( const T &t ) :
                data( t ) {}

        Custom( const Custom< long double > &t ) :
                data( t.value() ) {}
        
        const T& value() const {
            return data;
        }

    private:
        T data;
};

#define CUSTOM_OPERATOR( OP )\
template< typename T, typename U >\
const Custom< long double > operator OP ( const Custom< T > &A, const Custom< U > &B ) {\
    return A.value() OP B.value();\
}

CUSTOM_OPERATOR( + )

CUSTOM_OPERATOR( - )

CUSTOM_OPERATOR( << )

CUSTOM_OPERATOR( >> )

CUSTOM_OPERATOR( / )

CUSTOM_OPERATOR( * )

int main( int, char** ) {

    {
        int             AS = 1;
        float           BS = 1.1;
        float           CS = AS + BS;
        float           DS = BS + AS;

        Custom<int>     A( AS );
        Custom<float>   B( BS );
        Custom<float>   C = A + B;
        Custom<float>   D = B + A;

        std::cout << "standard:\t" << CS << "\ncustom:\t\t" << C.value() << std::endl;
        std::cout << "standard flipped:\t" << DS << "\ncustom flipped:\t\t" << D.value() << std::endl;
    }

    {
        double          AS = std::numeric_limits<double>::max() / 2;
        float           BS = 1.1;
        double          CS = AS + BS;
        double          DS = BS + AS;

        Custom<double>  A( AS );
        Custom<float>   B( BS );
        Custom<double>  C = A + B;
        Custom<double>  D = B + A;

        std::cout << "standard:\t" << CS << "\ncustom:\t\t" << C.value() << std::endl;
        std::cout << "standard flipped:\t" << DS << "\ncustom flipped:\t\t" << D.value() << std::endl;
    }

    {
        int64_t         AS = std::numeric_limits<int64_t>::max() / 2;
        float           BS = 1.1;
        int64_t         CS = AS + BS;
        int64_t         DS = BS + AS;

        Custom<int64_t> A( AS );
        Custom<float>   B( BS );
        Custom<int64_t> C = A + B;
        Custom<int64_t> D = B + A;

        std::cout << "standard:\t" << CS << "\ncustom:\t\t" << C.value() << std::endl;
        std::cout << "standard flipped:\t" << DS << "\ncustom flipped:\t\t" << D.value() << std::endl;
    }

    {
        int64_t         AS = std::numeric_limits<int64_t>::max() / 2;
        uint8_t         BS = 1;
        int64_t         CS = AS + BS;
        int64_t         DS = BS + AS;

        Custom<int64_t> A( AS );
        Custom<uint8_t> B( BS );
        Custom<int64_t> C = A + B;
        Custom<int64_t> D = B + A;

        std::cout << "standard:\t" << CS << "\ncustom:\t\t" << C.value() << std::endl;
        std::cout << "standard flipped:\t" << DS << "\ncustom flipped:\t\t" << D.value() << std::endl;
    }

    return 0;
}


Of dit echt leuk is qua snelheid/geheugen valt nog te bezien :p

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Arjan schreef op dinsdag 04 augustus 2009 @ 23:50:
als het alleen maar correct hoeft is dit wellicht een oplossing
C++:
1
...


Of dit echt leuk is qua snelheid/geheugen valt nog te bezien :p
Dit hoeft niet waar te zijn.
Templates zijn per definitie te inlinen door de compiler dus dan heeft ie ook de mogelijkheid om die long double cast er van tussen te halen en rechtstreeks naar het eindtype te casten. Of ie dit doet is een ander paar mouwen.

Ik zou die oplossing van .oisyn toch even overwegen. Het is code die eenmalig moet geschreven worden en je daarna diep in een header kan begraven.

@xos:
ik zou my_type geen operator() geven. Dit is plain wrong aangezien een type geen functor/predicate of andere eigenschappen bezit. Zeker al niet als het wrappers zijn rond C++ types: ooit het volgende al gezien?
C++:
1
2
int a = 10;
b = a();

niet doen dus ;)

Verder is je oplossing met expressions wel netjes. Ik vraag me af welke assembly daaruit komt rollen.

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
H!GHGuY schreef op woensdag 05 augustus 2009 @ 07:40:
[...]


Dit hoeft niet waar te zijn.
Templates zijn per definitie te inlinen door de compiler dus dan heeft ie ook de mogelijkheid om die long double cast er van tussen te halen en rechtstreeks naar het eindtype te casten. Of ie dit doet is een ander paar mouwen.
Heb ik ook over nagedacht, maar ik kan me niet voorstellen dat de compiler dit doet voor custom data typen.
Ik zou die oplossing van .oisyn toch even overwegen. Het is code die eenmalig moet geschreven worden en je daarna diep in een header kan begraven.
[...]
Ik heb .oisyns code zitten bestuderen, maar volgens mij zit er technisch geen verschil in zijn methode en de mijne waarbij je dus voor elke combinatie het returntype moet defineren.

Syntactisch is de oplossing van .oisyn natuurlijk veel interesanter aangezien je deze return type voor elk class kan gebruiken. In ieder geval moet je alle combinaties uitschrijven aangezien er geen impliciete conversies gaan plaatsvinden.

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

H!GHGuY schreef op dinsdag 04 augustus 2009 @ 21:57:
De compiler neemt jouw operator niet in beschouwing aangezien hij een operator zoekt voor
C++:
1
?? operator+(Custom<int>&, Custom<float>&)

Vermoedelijk neemt de compiler hierbij geen getemplate conversies in beschouwing, als hij al impliciete conversies in beschouwing neemt.
.oisyn, als C++ kenner zal hier wel verder over kunnen uitbreiden.
Wat hij doet is alle operator+ definities pakken die zichtbaar zijn, en daar vervolgens de beste match bij vinden. Aangezien er een operator+(Custom<T>, Custom<U>) bestaat, zal hij gewoon die pakken. Sowieso zal hij niet met de built-in operators matchen (zoals operator+(int, float)), omdat er geen conversies bestaan van je Custom type naar het built-in type.

Het funeste van dit probleem is dat je een return-type moet definieren voor je functie, ookal zou de compiler dat best kunnen deduceren. Doe maar eens dit:
C++:
1
2
3
4
5
6
7
8
template<class T> void OutputType(T) { std::cout << typeid(T).name() << std::endl; }

int main()
{
    Custom<int> a;
    Custom<float> b;
    OutputType(a.value() + b.value())
}

Het resultaat zal "float" zijn. Hij weet het dus dondersgoed. Het probleem is dat er geen syntax bestaat om het type van een expressie te gebruiken, en dat is de hele reden waarom decltype() is geïntroduceerd in C++0x.

Je kunt overigens wel wat in elkaar hacken zodat hij altijd de ingebouwde regels van de compiler volgt, ipv zelf de promotie en conversieregels te herhalen:
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
#include <iostream>

template<class T> struct identity { typedef T type; };
template<class T, int N> struct identity_and_value { typedef T type; static const int value = N; };

template<int N> struct builtin_type;
template<> struct builtin_type<1> : identity_and_value<char, 1> { };
template<> struct builtin_type<2> : identity_and_value<signed char, 2> { };
template<> struct builtin_type<3> : identity_and_value<unsigned char, 3> { };
template<> struct builtin_type<4> : identity_and_value<signed short, 4> { };
template<> struct builtin_type<5> : identity_and_value<unsigned short, 5> { };
template<> struct builtin_type<6> : identity_and_value<signed int, 6> { };
template<> struct builtin_type<7> : identity_and_value<unsigned int, 7> { };
template<> struct builtin_type<8> : identity_and_value<signed long, 8> { };
template<> struct builtin_type<9> : identity_and_value<unsigned long, 9> { };
template<> struct builtin_type<10> : identity_and_value<signed long long, 10> { };
template<> struct builtin_type<11> : identity_and_value<unsigned long long, 11> { };
template<> struct builtin_type<12> : identity_and_value<float, 12> { };
template<> struct builtin_type<13> : identity_and_value<double, 13> { };
template<> struct builtin_type<14> : identity_and_value<long double, 14> { };

template<class T, class U> struct is_identical { static const bool value = false; };
template<class T> struct is_identical<T, T> { static const bool value = true; };

template<class T, int N, bool B = is_identical<T, typename builtin_type<N>::type>::value >
    struct do_find_builtin_type : do_find_builtin_type<T, N+1> { };

template<class T, int N> struct do_find_builtin_type<T, N, true> : identity_and_value<T, N> { };

template<class T> struct find_builtin_type : do_find_builtin_type<T, 1> { };




template<class T, class U> struct mathresult_number
{
    template<class V> struct numberof { numberof(V); char data[find_builtin_type<V>::value]; };
    template<class V> static numberof<V> getnumberof(V);
    static const int value = sizeof(getnumberof(T() + U()));
};

template<class T, class U> struct mathresult : identity<typename builtin_type<mathresult_number<T, U>::value>::type> { };


template<class T> 
class Custom
{ 
public: 
    Custom() : data() { }
    Custom(const T &t) : data(t) { }

    template<typename U> operator Custom<U>()
    { 
        return Custom<U>(this->value()); 
    } 

    const T& value() const
    { 
        return data; 
    } 

private: 
    T data; 
}; 

template<class T, class U> 
Custom<typename mathresult<T,U>::type> 
    operator+(const Custom<T> & a, const Custom<U> & b) 
{
    return a.value() + b.value();
}

int main()
{
    Custom<int> a;
    Custom<float> b;
    Custom<unsigned long> c;
    Custom<double> d;
    std::cout << typeid(a + b).name() << std::endl;
    std::cout << typeid(a + c).name() << std::endl;
    std::cout << typeid(b + d).name() << std::endl;
    std::cout << typeid(c + d).name() << std::endl;
}


de mathresult_number::getnumberof() functie is de kern van het geheel, en hanteert hetzelfde principe als de OutputType() functie uit het voorbeeld daarboven. Hij returnt een struct van een bepaalde grootte, en aan de hand van die grootte kunnen we zien welk built-in type de compiler heeft gededuceerd voor de optelling. De rest is wat template magic om het juiste nummer bij een type te vinden e.d.

[ Voor 4% gewijzigd door .oisyn op 05-08-2009 12:27 ]

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.


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

.oisyn schreef op woensdag 05 augustus 2009 @ 12:15:
[...]
Wat hij doet is alle operator+ definities pakken die zichtbaar zijn, en daar vervolgens de beste match bij vinden. Aangezien er een operator+(Custom<T>, Custom<U>) bestaat, zal hij gewoon die pakken. Sowieso zal hij niet met de built-in operators matchen (zoals operator+(int, float)), omdat er geen conversies bestaan van je Custom type naar het built-in type.
De TS had het over de vorige oplossing met tweemaal Custom<T>.

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
Damn .oisyn, erg gaaf!

Ik snap wat je doet, maar ben al een tijdje bezig om te snappen hoé je 't doet :D

wat die conversie betreft, dat sloeg inderdaad op de variant met tweemaal T.

dingen als
C++:
1
2
3
Custom<int> a;
float b;
Custom<float> c = a * b;

accepteerd ie wel door b te converteren via Custom<float>( b )..

/edit

waarom hebben ze trouwens besloten om decltype toe te voegen inplaats van bijvoorbeeld "typeof( T() + N() )" toe te staan ?

[ Voor 16% gewijzigd door Arjan op 05-08-2009 14:32 ]

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ik snap niet helemaal wat je met die vraag bedoelt. Gaat het je puur om de naamgeving (waarom "decltype" en niet "typeof")?

typeof wordt al ondersteund door sommige compilers, maar doet iets anders dan decltype, wat staat voor declared type. Volgens mij hanteert een gangbare implementatie van typeof dezelfde regels als voor template argument deduction en de typeid operator.

C++:
1
2
3
4
5
6
int main()
{
    int i;
    int & ref = i;
    OutputType(ref);
}

Dit zal "int" outputten, omdat bij template argument deduction nooit een reference gemaakt zal worden. typeof(ref) is daarom int, maar decltype(ref) is int&. Dat is van belang omdat je meestal letterlijk het type over wilt nemen.

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.


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

H!GHGuY schreef op woensdag 05 augustus 2009 @ 13:35:
[...]

De TS had het over de vorige oplossing met tweemaal Custom<T>.
Ah, nu de topic nog eens helemaal doorgelezen te hebben denk ik dat ik de vraag snap. Dus de vraag is: waarom kan de compiler niet de juiste conversie kiezen als je een Custom<int> bij een Custom<float> optelt, en er bestaat alleen een operator+(Custom<T>, Custom<T>) (en Custom<T> heeft de juiste conversie operaties gedefinieerd naar Custom<U>). Klopt dit?

Dat is omdat de compiler helemaal geen rekening houdt bij willekeurige functies met de promotie en conversieregels. Dit zal ook niet compilen:
C++:
1
2
3
4
5
6
void foo(int, int);
void foo(float, float);
int main()
{
    foo(3, 4.5f);
}

De error zal zijn dat foo ambigu is - het kan zowel foo(int, int) als foo(float, float) zijn. Voor beide varianten moet er iig 1 type geconverteerd worden, dus beide matches zijn net zo "duur".

Met je template operator ligt het nog veel ingewikkelder, want dan moet ie het type ook nog eens gaan deduceren. Waarom zou hij besluiten dat een conversie van Custom<int> naar Custom<float> goedkoper is dan andersom? Wat de compiler betreft ziet ie gewoon maar 2 verschillende typen. Dat de ene een int als template parameter heeft en de andere een float doet er niet zo heel veel toe. De relatie tussen Custom<int> en Custom<float> heeft niets te maken met de relatie tussen int en float. Hij kan dus niet zomaar besluiten om T te deduceren naar float en de Custom<int> te converteren. Andersom is namelijk net zo goed mogelijk.

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.


Acties:
  • 0 Henk 'm!

  • coubertin119
  • Registratie: Augustus 2002
  • Laatst online: 15-09 17:06
.oisyn schreef op woensdag 05 augustus 2009 @ 17:41:
[...]Met je template operator ligt het nog veel ingewikkelder, want dan moet ie het type ook nog eens gaan deduceren. Waarom zou hij besluiten dat een conversie van Custom<int> naar Custom<float> goedkoper is dan andersom? Wat de compiler betreft ziet ie gewoon maar 2 verschillende typen. Dat de ene een int als template parameter heeft en de andere een float doet er niet zo heel veel toe. De relatie tussen Custom<int> en Custom<float> heeft niets te maken met de relatie tussen int en float. Hij kan dus niet zomaar besluiten om T te deduceren naar float en de Custom<int> te converteren. Andersom is namelijk net zo goed mogelijk.
Hier ging mijn ideetje over, je zorgt voor een specialization van je constructor voor andere types, dus:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template<class T>
class Custom {
    public:
        ...

        template<typename U>
        Custom(const Custom<U> &t)
         : data(t.value())
        {};
    ...
};

template<class int>
class Custom {
    public:
        ...

        template<typename float>
        Custom(const Custom<float> &t)
         : data(t.value())
        {};
    ...
};

Forgive me if I'm wrong, maar kan het doordat de compiler nu 1 gespecialiseerde variant aangeboden krijgt wordt de overload resolution mogelijk gemaakt? "Andorsom is net zo goed mogelijk" wordt nu toch teniet gedaan door die specialization, als de compiler een meer gespecialiseerde functie/methode voor de kiezen krijgt kiest hij daar meteen voor, zonder andere correcte maar algemenere varianten te beschouwen, if I remember correctly.


@H!GHGuY: Uiteraard, maar ik was niet helemaal zeker van de syntax, dus speelde ik op veilig :). * coubertin119 hides in shame

[ Voor 4% gewijzigd door coubertin119 op 06-08-2009 10:59 ]

Skat! Skat! Skat!


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Dan kun je beter explicit instantiation doen dan telkens je hele class te herhalen...

@coubertin119: Ik was ook niet zeker of die explicit instantiation van template functions binnen template class zelfs kon, dus heb ik even gegoogled ;) no shame, dus!

Hier vind je trouwens de correcte syntax:
http://publib.boulder.ibm...xplicit_instantiation.htm

[ Voor 76% gewijzigd door H!GHGuY op 06-08-2009 16:35 ]

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

coubertin119 schreef op donderdag 06 augustus 2009 @ 09:14:
Forgive me if I'm wrong, maar kan het doordat de compiler nu 1 gespecialiseerde variant aangeboden krijgt wordt de overload resolution mogelijk gemaakt? "Andorsom is net zo goed mogelijk"
wordt nu toch teniet gedaan door die specialization, als de compiler een meer gespecialiseerde functie/methode voor de kiezen krijgt kiest hij daar meteen voor, zonder andere correcte maar algemenere varianten te beschouwen, if I remember correctly.
Nee, die regel geldt alleen voor overloads van dezelfde functie - als er een meer specialized versie bestaat van die functie, dan heeft die versie voorrang. Dat er nu een conversie meer specialized is maakt voor de overload resolution van de operator+ dus niet uit. Bovendien zit je nog steeds met het probleem dat ik eerder liet zien met foo(int, int) en foo(float, float), met een aanroep van foo(3, 4.5f).

Wat natuurlijk wél kan, is dat je de conversie van Custom<float> naar Custom<int> explicit maakt (wat sowieso wel een zinnige feature zou zijn - je wilt een float niet altijd impliciet casten kunnen naar een int). Als er dan een operator+(Custom<float>, Custom<float>) bestaat en een operator+(Custom<int>, Custom<int>), dan wordt de float versie aangeroepen als je 'm een Custom<float> en een Custom<int> geeft. Dit werkt echter weer niet met template argument deduction. Bij een operator+(Custom<T>, Custom<T>) zal hij alsnog niet weten welke kant hij T op moet deduceren, ookal zal de T=int versie uiteindelijk niet voldoen. Dit komt omdat hij de deduction doet voor de overload resolution (en het is niet zo dat de template dan gewoon twee keer meedoet, een keer voor T=int en voor T=float)

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.


Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Ik mis iets, maar waarom wil je zo nodig het type van a+b uitrekenen?

C++:
1
2
3
4
5
6
7
8
9
10
11
template <typename T, typename U> struct plusresult {
  T t; U u;
  plusresult(T t, U u)  : t(t), u(u) { }
  operator R() { return t +u; }
};
template <typename T, typename U> plusresult<T,U> operator + (T t, U u)
{
   return plusresult<T,U>(t, u);
}

Custom<float> c = Custom<int>(1) + Custom<float>(0.5);

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


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ah ja die is ook wel aardig :)

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.


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
C++:
1
operator R()
:?

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Daar moet een "template<class R>" voor (was me trouwens niet eens opgevallen voor jouw post ;))

[ Voor 43% gewijzigd door .oisyn op 07-08-2009 01:09 ]

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.


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

De code zal trouwens nog niet werken ;)
C++:
1
2
3
4
5
6
7
8
9
10
template <typename T, typename U> struct plusresult {
  T t; U u;
  plusresult(T t, U u)  : t(t), u(u) { }
  template<typename R> operator Custom<R>() { return Custom<R>(t +u); }
}; 

template <typename T, typename U> plusresult<T,U> operator + (Custom<T> t, Custom<U> u)
{
   return plusresult<T,U >(t.value(), u.value());
} 

Anders moet je alsnog een operator+ definieren voor Custom<T> en Custom<U> en dan krijgt die ook precedence op deze getemplate versie.

Ik denk zelfs dat je met MSalters voorbeeld an sich in een eindeloze lus kunt raken...

[ Voor 11% gewijzigd door H!GHGuY op 07-08-2009 10:00 ]

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
H!GHGuY schreef op vrijdag 07 augustus 2009 @ 09:57:
De code zal trouwens nog niet werken ;)
C++:
1
2
3
4
5
6
7
8
9
10
template <typename T, typename U> struct plusresult {
  T t; U u;
  plusresult(T t, U u)  : t(t), u(u) { }
  template<typename R> operator Custom<R>() { return Custom<R>(t +u); }
}; 

template <typename T, typename U> plusresult<T,U> operator + (Custom<T> t, Custom<U> u)
{
   return plusresult<T,U >(t.value(), u.value());
} 

Anders moet je alsnog een operator+ definieren voor Custom<T> en Custom<U> en dan krijgt die ook precedence op deze getemplate versie.
Deze aangepaste versie werkt inderdaad prima.
Ik denk zelfs dat je met MSalters voorbeeld an sich in een eindeloze lus kunt raken...
Klopt ja, heb het getest en hij komt in een eindeloze loop. ik vind het wel een enorm leuke constructie, moet toch eens wat meer met template metaprogramming gaan doen :)

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
even kijken of ik het gesnapt heb, want ik krijg het nog niet helemaal werkend :)

[code=c++]
#include <iostream>

template< class T >
class Custom {

public:
Custom( const T &t ) :
data( t ) {}

const T& value() const {
return data;
}

private:
T data;
};

template < typename T, typename U >
struct plusresult {
plusresult( T t, U u ) :
t( t ),
u( u ) { }
template< typename R > operator Custom< R >() {
return Custom< R >( t + u );
}
T t;
U u;
};

template < typename T, typename U > plusresult< T, U > operator + ( Custom< T > t, Custom< U > u )
{
return plusresult< T, U >( t.value(), u.value() );
}

int main( int, char** ) {

Custom< int > a = 2;
Custom< float > b = 3.1;
Custom< float > c = 4.7;
Custom< float > d = a + b + c;

std::cout << c.value() << std::endl;

return 0;
}
[/code=c++]
Wat er volgens mij op regel 40 gebeurd:
C++:
1
a + b -> Custom< int > + Custom< float > -> plusresult< int, float >( a+b )

C++:
1
plusresult< int, float >( a+b ) + Custom< float > -> ?

Op dit moment loopt de compiler vast, omdat hij kennelijk niet ziet dat hij zowel plusresult< int, float > naar een Custom< float > kan casten en dat hij de 2 naar een Custom< int > kan casten.

Kan hij dit laatste niet omdat deze conversie vanuit een template geinstantieerd wordt :?

[ Voor 0% gewijzigd door Arjan op 07-08-2009 17:29 . Reden: fout in het tweede code blok ]

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Hij kan een plusresult<int, float> naar alles casten. Er is tenslotte een operator Custom<R>, en die R ligt niet vast. Hij kan dus de operator+ template arguments niet deduceren.

[ Voor 50% gewijzigd door .oisyn op 07-08-2009 17:17 ]

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.


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

MSalters voorbeeld kan volgens mij niet gebruikt worden voor meer dan 2 operanden zonder compromissen...

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
.oisyn schreef op vrijdag 07 augustus 2009 @ 17:16:
Hij kan een plusresult<int, float> naar alles casten. Er is tenslotte een operator Custom<R>, en die R ligt niet vast. Hij kan dus de operator+ template arguments niet deduceren.
Maar om dit statement te resolven moet hij sowieso twee Custom instances gaan optellen, en die operator is bekend..
main.cpp:40: error: no match for operator+ in operator+ [with T = int, U = float](a, b) + 

(stond een fout in m'n tweede code blok, heb het nu verbeterd)

werkt de compiler soms zonder lookahead bij het resolven van dit soort problemen?

[ Voor 20% gewijzigd door Arjan op 07-08-2009 17:35 . Reden: falende quotes verwijderd ]

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Arjan schreef op vrijdag 07 augustus 2009 @ 17:30:
[...]

Maar om dit statement te resolven moet hij sowieso twee Custom instances gaan optellen, en die operator is bekend..
Nee, want a+b is geen Custom maar een plusresult. Een plusresult is wel te casten naar een Custom, maar naar elk mogelijke Custom. De operator+ verwacht ook een generieke custom, dus wat moet R dan zijn? float? int? std::string?

Ik vind het bij nader inzien ook helemaal geen mooie oplossing. Het is volgens mij onmogelijk om generieke operators te definieren voor zowel plusresult als Custom, terwijl je gebruik blijft maken van de operator Custom<R> feature. Zodra je expressies gaat nesten heb je weer specifieke types nodig. Dan kun je het wel generiek houden tot de assignment, en dan het type gebruiken waaraan je assignt, maar dan cast je mogelijk te vroeg (een float+float+float aan een int assignen zal dan de eerste float+float naar int casten, en dan pas de derde float erbij optellen, en dan weer naar int casten, wat natuurlijk fout is)

Geef mij maar gewoon C++0x:
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
template<class T>  
class Custom 
{  
public:  
    Custom() : data() { } 
    Custom(const T &t) : data(t) { } 

    template<typename U> operator Custom<U>() 
    {  
        return Custom<U>(this->value());  
    }  

    const T& value() const 
    {  
        return data;  
    }  

private:  
    T data;  
};  

template<class T, class U>  
Custom<decltype(T() + U())>  
    operator+(const Custom<T> & a, const Custom<U> & b)  
{ 
    return a.value() + b.value(); 
}

Klaar :P

[ Voor 20% gewijzigd door .oisyn op 07-08-2009 17:45 ]

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.


Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Ja, zeg, 'k heb niet voor niets voor dat feature gestemd ja :P

Het altenatief is inderdaad je berekening zo lang mogelijk uitstellen. Niet mijn uitvinding, het komt uit Blitz++. En zoals eig. al duidelijk was uit de missende "template <typename R>" was dit een snelle schets. Het kernidee is dat het bepalen van het type Custom<Resultaat> overlaat aan de Template Argument Deduction van de compiler.

Wat betreft het optellen van meerdere argumenten geldt inderdaad dat je er nog niet bent met alleen de non-member operator+. Je moet ook plusresult<T,U>::operator+(V v) definieren zodat de compiler in staat is om het type van t+u+v te bepalen. En ja, dat kan best snel best complex worden, als je alle operators wil ondersteunen. (a+b) && (!c - d) is nu eenmaal een valide expressie met een redelijk ingewikkelde parse tree. Het type daarvan kan de compiler veel beter bepalen dan jij, maar je moet 'm wel de kans geven.

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


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
De noodzaak van decltype is meer dan voldoende duidelijk gemaakt :D

hoewel de volgende code toch een aardig end in de richting komt:
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
template< typename T >
class Custom { ... }

#define OPERATOR( OP, NAME, CLASS )\
template < typename T, typename U >\
        class Result##CLASS##NAME {\
    public:\
        Result##CLASS##NAME( T t, U u ) :\
                t( t ),\
                u( u ) { }\
        template< typename R >\
        operator CLASS< R >() {\
            return CLASS< R >( t OP u );\
        }\
        template< typename R >\
        CLASS< R > operator + ( const CLASS< R > &r ) {\
            return CLASS< R >( t OP u + r.value() );\
        }\
        template< typename R >\
        CLASS< R > operator - ( const CLASS< R > &r ) {\
            return CLASS< R >( t OP u - r.value() );\
        }\
        template< typename R >\
        CLASS< R > operator * ( const CLASS< R > &r ) {\
            return CLASS< R >( t OP u * r.value() );\
        }\
        template< typename R >\
        CLASS< R > operator / ( const CLASS< R > &r ) {\
            return CLASS< R >( t OP u / r.value() );\
        }\
    private:\
        T t;\
        U u;\
};\
template < typename T, typename U > Result##CLASS##NAME< T, U > operator OP ( const CLASS< T > &t, const CLASS< U > &u ) {\
    return Result##CLASS##NAME< T, U >( t.value(), u.value() );\
}

OPERATOR( +, Plus, Custom )

OPERATOR( -, Minus, Custom )

OPERATOR( *, Multiply, Custom )

OPERATOR( /, Divide, Custom )

Het enige praktische bezwaar dat ik hierbij nog heb, is dat het niet mogelijk is om
C++:
1
Custom<int>( 2 ) * Custom<int>( 2 ) * 2

te doen, hetgeen toch wel een basale functie is.

al met al een leuke exercitie die me veel geleerd heeft over de werking van templates :)

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Arjan schreef op zaterdag 08 augustus 2009 @ 00:09:
C++:
1
2
3
...
#define ...
...

Het enige praktische bezwaar dat ik hierbij nog heb, is dat het niet mogelijk is om
C++:
1
Custom<int>( 2 ) * Custom<int>( 2 ) * 2

te doen, hetgeen toch wel een basale functie is.

al met al een leuke exercitie die me veel geleerd heeft over de werking van templates :)
Ik hoop dat je die defines niet live gaat gebruiken.

Een tijd terug moest ik kiezen tussen een meer C-style approach met defines of een pure C++-style approach met template-based virtual inheritance en andere meuk. Ik beklaag me nog helemaal niet dat ik toen de C++-style gekozen heb. Binnen C++ gebruik je defines echt liever niet voor dat soort zaken.
Je code wordt onoverzichtelijk, verward en niet onderhoudbaar. Mijn voorkeur zou toch uitgaan naar de code van .oisyn.

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
Mijn initiele vraag ontstond omdat ik in twee data typen wilde voorzien voor de library. Een beetje zoals Qt doet met haar QPoint en QPointF.
Ik heb effectief dus maar twee typen vastgelegd. Voor deze situatie heb ik dan ook gewoon gekozen om de operators expliciet vast te leggen. Als gebruikers dan een eigen variant met bijvoorbeeld doubles willen gebruiken, dan is het hun eigen verantwoordelijkheid om de bijbehorende operators aan te maken :)

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

H!GHGuY schreef op zaterdag 08 augustus 2009 @ 08:05:
Je code wordt onoverzichtelijk, verward en niet onderhoudbaar.
Mwoa, dat is een discussiepunt. Het alternatief is dat je code steeds moet herhalen, dat is ook niet onderhoudbaar. Vooral bij zaken als een eigen implementatie van iets als std::tr1::function, waarbij je (zolang je C++0x's variadic templates nog niet hebt) N specialisaties moet maken om functies tot N parameters te supporten, dan doe je dat toch liever met een preprocessor constructie zodat je maar 1 daadwerkelijke implementatie hebt.

Wat dan overigens wel een aardige techniek is is om de implementatie niet in een macro te zetten, maar in een aparte header file (uiteraard zonder header guard). Vervolgens include je die header file dan meerdere keren, met steeds andere waarden voor bepaalde macro's, waar die header weer gebruik van maakt. Dan blijft je source leesbaar, je IDE snapt het beter en je hebt niet van die lelijke \'s nodig aan het eind van elke regel :)

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.


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

.oisyn schreef op zaterdag 08 augustus 2009 @ 12:17:
Wat dan overigens wel een aardige techniek is is om de implementatie niet in een macro te zetten, maar in een aparte header file (uiteraard zonder header guard). Vervolgens include je die header file dan meerdere keren, met steeds andere waarden voor bepaalde macro's, waar die header weer gebruik van maakt. Dan blijft je source leesbaar, je IDE snapt het beter en je hebt niet van die lelijke \'s nodig aan het eind van elke regel :)
Het ging me eigenlijk vooral om de multi-line define's die een hele class definieren. Het is lelijk en doet niemand eer aan.

ASSUME makes an ASS out of U and ME


  • cpt.spiff
  • Registratie: Augustus 2009
  • Laatst online: 20-02 14:29
Sorry, ik post een beetje als mosterd na de maaltijd, maar ik denk dat er een iets simpeler antwoord is om het originele probleem op te lossen. Ik stuit een beetje laat op deze thread.

Met behulp van de sizeof-trick kun je een meta-functie maken die het correcte returntype van a + b geeft, mits dat returntype A of B is (dus óf het type van a, óf het type van b). Dit werkt dan voor alle types die aan deze voorwaarde voldoen, niet alleen de builtin-types (dus ook voor b.v. het optellen van std::complex<T> en T).

Dit is een voorbeeld voor zo'n metafunctie voor de plus-operator. Het resultaat van T() + U() wordt hier gerepresenteerd door plusreturn<T,U>::type (let wel: de 'plusreturn' metafunctie in deze code heeft niets te maken met de 'plusresult' expression template in de post van MSalters):

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
// rudimentaire versie van boost::mpl::if_
template< bool usefirst, typename Then, typename Else>
struct if_
{
    typedef Then type;
};

template< typename Then, typename Else>
struct if_<false, Then, Else>
{
    typedef Else type;
};


// metafunctie 'plusreturn'
// accepteert twee type-argumenten ('First' en 'Second')
// en returnt het type van First() + Second().
// condities:
// -First en Second zijn default constructible
// -First() + Second() return iets van type First OF Second.
// de eerste conditie is niet strict noodzakelijk, maar
// maakt deze implementatie wel beter leesbaar.
template< typename First, typename Second>
struct plusreturn
{
    typedef char firsttype[1];
    typedef char secondtype[2];

    static firsttype &which( const First &);
    static secondtype &which( const Second &);
    typedef
        typename if_<
            sizeof( which( First() + Second())) == sizeof(firsttype),
            First,
            Second>::type type;
};

// specialisatie als First == Second
template< typename OnlyType>
struct plusreturn< OnlyType, OnlyType>
{
    typedef OnlyType type;
};


En voor het 'Custom' template van de TS kun je daarmee als volgt een operator+ definieren

C++:
1
2
3
4
5
6
7
8
template<typename First, typename Second>
Custom< typename plusreturn<First, Second>::type>
    operator+(
            const Custom<First>  &lhs,
            const Custom<Second> &rhs)
    {
        return Custom< typename plusreturn<First, Second>::type>( lhs.value() + rhs.value());
    }


In het algemeen kun je de sizeof-trick gebruiken als je eigenlijk decltype nodig had en je (binnen een context) de resulterende types kunt beperken tot een handjevol (in dit geval dus tot twee).

Leuke thread. Dit soort problemen zou je iedere week moeten posten, dan kun je het de 'Gathering of Tweakers Weekly', oftewel GoTW noemen ;)

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Hey, da's eigenlijk wel een goede. Doet hetzelfde als wat ik hier doe, maar ik kijk dan alleen naar alle built-in types. Het is idd veel logischer om gewoon de twee types van de input te controleren. Nice :)

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.


  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
Ik heb ook over deze mogelijkheid nagedacht, maar volgens mij kom je in de problemen omdat sizeof( int ) == sizeof( float )

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Nee, want sizeof werkt niet op die types, maar op char arrays (firsttype en secondtype, van resp. 1 en 2 bytes). Hij doet echt exact hetzelfde als wat ik al deed, behalve dat ik dan alle built-in types check, en hij de twee opgegeven types in de expressie (echt stom dat dat niet in me is opgekomen)

[ Voor 77% gewijzigd door .oisyn op 14-08-2009 02:24 ]

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.


Acties:
  • 0 Henk 'm!

  • cpt.spiff
  • Registratie: Augustus 2009
  • Laatst online: 20-02 14:29
@.oisyn
Het lijkt inderdaad heel erg op jouw post. Bij het lezen van je code was me ontgaan dat je uiteindelijk ook gewoon de sizeof-trick gebruikt. Ik zag met name de linear search door de builtin-types.

@Arjan
Ook hier heeft .oisyn gelijk: ik doe niet sizeof( a +b) (dus b.v. sizeof( float)), maar sizeof( which( a + b)) en which is een functie met overloads die types van verschillende groottes returnt. Dat is ook de vorm die de sizeof-trick meestal heeft: gebruik overloads die een returntype van een gecontroleerde grootte hebben, waardoor je als het ware een compile-time nummer kunt uitdelen aan verschillende types.

Acties:
  • 0 Henk 'm!

  • coubertin119
  • Registratie: Augustus 2002
  • Laatst online: 15-09 17:06
Je kan eventueel paragraaf 15.2.4 Promotion Traits lezen uit C++ Templates: The Complete Guide, van Vandevoorde en Josuttis. Die gaat over dit soort materie. De oplossing van cpt.spiff lijkt mij handiger dan wat zij voorstellen, maar een beetje extra leesvoer kan nooit kwaad :).

Skat! Skat! Skat!

Pagina: 1