Toon posts:

[C++] Signals/Slots, adres van argument nemen in template

Pagina: 1
Acties:

Onderwerpen


  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
Ik heb een signal / slot library gemaakt voor C++ en ben nog bezig om wat laatste kreukjes eruit te strijken.

Een van de issues die mij op het moment het meest stoort is 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
#include <sas/slot>
#include <sas/signal>

#include <string>
#include <iostream>

void test( std::string &str )
{
    std::cerr << &str << std::endl;
    std::cerr << str << std::endl;
    str = "woei";
    std::cerr << str << std::endl;
}
int main()
{
    sas::signal signal;

    const std::string aap( "aap" );

    signal.connect( test, "mijnVar" );

    signal.emit( "mijnVar", aap );

    std::cerr << &aap << std::endl;
    std::cerr << aap << std::endl;
}

00CAFCB0
aap
woei
00CAFCB0
woei
niet zo netjes aangezien er een const variabele is aangepast.

Het probleem ontstaat omdat ik in de emit call de adressen van de argumenten wil opslaan. Dit doe ik door een const reference te nemen van de argumenten, waar ik vervolgens het adres van pak. Door deze const reference kan ik echter niet afleiden of het argument startte als een const of non-const object.

Als ik echter de template herschrijf zodat deze een non-const reference accepteert dan heb ik het probleem dat hij een r-value niet accepteert en dat kan natuurlijk ook niet de bedoeling zijn :)

Dus de open vraag: hoe zou ik dit kunnen oplossen?

alvast bedankt!

De code is te vinden op: git://houbenweb.nl/SAS
(Zodra ik tevreden ben over het geheel maak ik een project pagina met documentatie)

[Voor 10% gewijzigd door Arjan op 19-05-2011 12:02]

oprecht vertrouwen wordt nooit geschaad


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:09

.oisyn

Moderator Devschuur® / Cryptocurrencies

Demotivational Speaker

Ik ga geen git client downloaden om je code door te lezen. Sowieso ga ik je code niet doorlezen. Maar er is weinig discussie mogelijk als je geen code toont.
Dit doe ik door een const reference te nemen van de argumenten, waar ik vervolgens het adres van pak. Door deze const reference kan ik echter niet afleiden of het argument startte als een const of non-const object.
Dus je doet een const_cast? Doe dat dan niet :).

[Voor 40% gewijzigd door .oisyn op 19-05-2011 12:17]

If I had a dollar for every time I didn't know what was going on, I'd be like: "Why am I always getting all this money?!"


  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
.oisyn schreef op donderdag 19 mei 2011 @ 12:15:
Ik ga geen git client downloaden om je code door te lezen. Sowieso ga ik je code niet doorlezen. Maar er is weinig discussie mogelijk als je geen code toont.


[...]

Dus je doet een const_cast? Doe dat dan niet :).
Omdat de code nog WIP is wilde ik 'm niet zomaar online gooien, maar hier staat ie: http://houbenweb.nl/sas

Als ik geen const_cast doe, dan kan een een object dat non-const is ook niet aanpassen :)

oprecht vertrouwen wordt nooit geschaad


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:09

.oisyn

Moderator Devschuur® / Cryptocurrencies

Demotivational Speaker

Nee duh. Daarom moet je ook geen const_cast doen :). Je zou ook een overload kunnen maken.

Maar zoals ik al zei, ik ga niet in je code lopen neuzen, je post maar gewoon relevante geisoleerde stukken code ;). In ieder geval van je interface en hoe je dit in grote lijnen hebt geimplementeerd.

If I had a dollar for every time I didn't know what was going on, I'd be like: "Why am I always getting all this money?!"


  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
I'll try:
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 A >
void emit( const std::string &nameA, const A &valueA )
{
    arguments arg( *this );
    arg.set( nameA, new argument< T >( valueA ) );
    call( arg );
}

template< class T >
class argument : public argument_base
{
    public:

        argument( const T &t ) :
                argument_base(),
                value( const_cast< T* >( &t ) ) { }

        T *value;

    private:

        argument( const argument< T >& ) :
                argument_base(),
                value() { }

        argument< T >& operator = ( const argument< T >& ) { return *this; }
};

Overloads lijkt me praktisch onmogelijk omdat je voor alle argumenten zowel const als non-const references moet hebben, waardoor het aantal mogelijkheden redelijk explodeert..

oprecht vertrouwen wordt nooit geschaad


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:09

.oisyn

Moderator Devschuur® / Cryptocurrencies

Demotivational Speaker

En hoe ziet je call() eruit?

If I had a dollar for every time I didn't know what was going on, I'd be like: "Why am I always getting all this money?!"


  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
C++:
1
2
3
4
5
6
7
8
9
10
11
void call( const arguments &arguments )
{
    try
    {
        ( ( *_class ).*( _member ) )( arguments );
    }
    catch( const invalid_type &err )
    {
        debug() << "Invalid type: " << err.error;
    }
}

arguments kun je zien als een lijst van, well sas::argument's :p

[Voor 13% gewijzigd door Arjan op 19-05-2011 13:10]

oprecht vertrouwen wordt nooit geschaad


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 04-06 17:19
Tja, 't is al Undefined Behavior als je de const_cast doet, daar hoef je niet eens de call voor te doen.

Voor de rest zou je moeten googlen op "C++ perfect forwarding"; dit is een bekend probleem in C++03 en is geaddresseerd in C++11.

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


  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
MSalters schreef op donderdag 19 mei 2011 @ 13:49:
Tja, 't is al Undefined Behavior als je de const_cast doet, daar hoef je niet eens de call voor te doen.

Voor de rest zou je moeten googlen op "C++ perfect forwarding"; dit is een bekend probleem in C++03 en is geaddresseerd in C++11.
Ah, interessant, dit is straks dus prima te adresseren, dan laat ik het zoals het is totdat C++11 de standaard is :)

Vraagje, waarom zou een const_cast in deze situatie undefined behaviour opleveren?
los natuurlijk van schrijven naar een const char* enzo. Maar dat is altijd een slecht idee, niet alleen in deze context.

oprecht vertrouwen wordt nooit geschaad


  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 06-06 09:59
Arjan schreef op donderdag 19 mei 2011 @ 14:09:
Vraagje, waarom zou een const_cast in deze situatie undefined behaviour opleveren?
Omdat het zo gespecificeerd is ;)

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
PrisonerOfPain schreef op donderdag 19 mei 2011 @ 14:19:
[...]
Omdat het zo gespecificeerd is ;)
zucht, c++...

Toch vraag ik me af of het in deze specifieke situatie een probleem is, de argumenten naar de functie moeten al op de stack staan, dus er is wel iets om het adres van te nemen zeg maar.

Maarja, dan maar copy by value tenzij c++11 gedetecteerd wordt.

oprecht vertrouwen wordt nooit geschaad


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:09

.oisyn

Moderator Devschuur® / Cryptocurrencies

Demotivational Speaker

MSalters schreef op donderdag 19 mei 2011 @ 13:49:
Vraagje, waarom zou een const_cast in deze situatie undefined behaviour opleveren?
Je string 'aap' kan mogelijk in ROM staan, in welk geval je 'm dus niet eens aan kúnt passen.

't Is trouwens ook alleen maar UB als het object in eerste instantie als const gedefinieerd is.
Arjan schreef op donderdag 19 mei 2011 @ 13:01:
C++:
1
2
3
4
5
6
7
8
9
10
11
void call( const arguments &arguments )
{
    try
    {
        ( ( *_class ).*( _member ) )( arguments );
    }
    catch( const invalid_type &err )
    {
        debug() << "Invalid type: " << err.error;
    }
}

arguments kun je zien als een lijst van, well sas::argument's :p
Je snapt toch ook wel dat ik geinteresseerd was hoe de call uiteindelijk bij test() terecht komt?

[Voor 39% gewijzigd door .oisyn op 19-05-2011 14:43]

If I had a dollar for every time I didn't know what was going on, I'd be like: "Why am I always getting all this money?!"


  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
.oisyn schreef op donderdag 19 mei 2011 @ 14:42:

[...]

Je snapt toch ook wel dat ik geinteresseerd was hoe de call uiteindelijk bij test() terecht komt?
haha, ja die schrijf ik toe aan post-lunch-syndroom :)
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
template < class T > T get( const std::string &name = std::string() ) const
{
    map::const_iterator item = _data.find( name );

    if ( item == _data.end() )
    {
        debug() << "Warning: No argument named \"" << name << "\" (" << get_type_name< T >() << ")";

        item = _missing.find( name );

        if ( item == _missing.end() )
        {
            argument_base *value = new argument_missing< typename argumentType< T >::Type >( typename argumentType< T >::Type() );

            item = const_cast< map& >( _missing ).insert( std::make_pair( name, value ) ).first;
        }
    }

    argument< typename argumentType< T >::Type >*parameter = dynamic_cast< argument< typename argumentType< T >::Type >* >( item->second );

    if ( !parameter )
    {
        std::string err( "parameter \"" + name + "\"" );

        err += " has type ";
        err += get_type_namePointer( item->second );
        err += " instead of ";
        err += get_type_name< argument< typename argumentType< T >::Type > >();

        throw invalid_type( err );
    }

    return *parameter->value;
}


template < class R, class A >
class argument_wrapper_1 : public slot
{
    public:

        argument_wrapper_1( R (*function)( A ), const std::string &name1 ) :
                _function( function ),
                _name1( name1 ) { }

        void wrapper( const arguments &arg )
        {
            _function( arg.get< A >( _name1 ) );
        }

    private:

        argument_wrapper_1< R, A >( const argument_wrapper_1< R, A >& ) :
                _function(),
                _name1() { }

        argument_wrapper_1< R, A >& operator = ( const argument_wrapper_1< R, A >& ) { return *this; }

        R (*_function)( A );
        const std::string _name1;
};

oprecht vertrouwen wordt nooit geschaad


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:09

.oisyn

Moderator Devschuur® / Cryptocurrencies

Demotivational Speaker

Ok, dus als ik het goed begrijp moeten types ook wel exact overeenkomen. Als ik een slot hebt die een Base verwacht, en ik pass een Derived, dan failt de call?

If I had a dollar for every time I didn't know what was going on, I'd be like: "Why am I always getting all this money?!"


  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
klopt, wellicht dat ik voor derived classes nog een uitzondering probeer te maken, implicit conversions wil ik niet supporten.

[edit]
hoewel dit ook prima zo is op te lossen:

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
struct Fruit
{
};

struct Appel : public Fruit
{
};

void appel( Appel )
{
}

void fruit( Fruit )
{
}

int main( int argc, const char *argv[] )
{
    sas::signal s;
    s.connect( appel, "a" );
    s.connect( fruit, "f" );

    Appel a;

    s.emit< Appel, Fruit >( "a", a, "f", a );

    return 0;
}

[Voor 60% gewijzigd door Arjan op 19-05-2011 15:48]

oprecht vertrouwen wordt nooit geschaad


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:09

.oisyn

Moderator Devschuur® / Cryptocurrencies

Demotivational Speaker

Dat lijkt me wel onwerkbaar eigenlijk :)

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
void test(float f) 
{ 
    std::cout << f << std::endl;
} 

int main() 
{ 
    sas::signal signal; 

    signal.connect(test, "mijnVar"); 
    signal.emit("mijnVar", 30); // oeps
    signal.emit("mijnVar", 30.0); // oeps
}


Maar goed, voor custom types is het natuurlijk ook zo goed als onimplementeerbaar.

[Voor 12% gewijzigd door .oisyn op 19-05-2011 16:11]

If I had a dollar for every time I didn't know what was going on, I'd be like: "Why am I always getting all this money?!"


  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
hm, ja voor built-ins zou het wel interessant kunnen zijn, ik zal er eens over nadenken, ben een beetje bang voor subtiele bugs die moeilijk op te merken zijn.

Het voordeel van de huidige setup is dat alles alleen maar werkt als de programmeur alles correct geconnect heeft, een incorrecte connectie geeft een melding

Er zijn wel meer handigheden die niet werken met deze library, neem bijvoorbeeld default arguments...
C++:
1
2
3
4
5
6
7
8
9
10
11
12
void test( float f = M_PI )  
{  
    std::cout << f << std::endl; 
}  

int main()  
{  
    sas::signal signal;  

    signal.connect(test, "nee, geen argument :p");  
    signal.emit(); 
}

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 04-06 17:19
Tja, er is een reden dat Boost typed signals heeft. Om het eerdere voorbeeld te hergebruiken:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
void test(double f, int g = 0) 
{ 
    std::cout << f << g << std::endl;
} 

int main() 
{ 
    signal<float> mijnVar; // Namen kunnen ook gewoon compile-time zijn. 

    mijnVar.connect(test); // Type conversie gebeurt hier.
    mijnVar.emit(30); // Geen probleem, roept signal<float>::emit(float) aan.
    mijnVar.emit(30.0); // double->float->double conversies, maar verder werkt het.
}

[Voor 7% gewijzigd door MSalters op 20-05-2011 13:18]

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:
  • 0Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
MSalters schreef op vrijdag 20 mei 2011 @ 13:17:
Tja, er is een reden dat Boost typed signals heeft. Om het eerdere voorbeeld te hergebruiken:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
void test(double f, int g = 0) 
{ 
    std::cout << f << g << std::endl;
} 

int main() 
{ 
    signal<float> mijnVar; // Namen kunnen ook gewoon compile-time zijn. 

    mijnVar.connect(test); // Type conversie gebeurt hier.
    mijnVar.emit(30); // Geen probleem, roept signal<float>::emit(float) aan.
    mijnVar.emit(30.0); // double->float->double conversies, maar verder werkt het.
}
Hoe kan boost hier de default parameter gebruiken? Zover ik weet is er geen manier om deze informatie te achterhalen..ie. de function pointer van test is void (*test)( double, int ).

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
Ik heb het nog gechecked met boost en zij gebruiken inderdaad ook niet de default parameter ( kan ook niet )
Daarnaast compiled bovenstaande niet, de boost variant ziet er zo uit:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
void test(double f, int g = 0)
{
    std::cout << f << g << std::endl;
}

int main()
{
    boost::signals2::signal< void (double,int)> mijnVar; // Namen kunnen ook gewoon compile-time zijn.

    mijnVar.connect(test); // Type conversie gebeurt hier.
    mijnVar( 30, 0 ); // Geen probleem, roept signal<float>::emit(float) aan.
    mijnVar( 30.0, 0 ); // double->float->double conversies, maar verder werkt het.
}


Het casten lijkt lastiger te implementeren dan aanvankelijk gedacht, ik heb momenteel een variant die wel werkt, maar ik niet super mooi in gebruik vind.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using namespace sas;

void test( double f )
{
    std::cout << f << std::endl;
}

int main()
{
    sas::signal mijnVar;

    mijnVar.connect( test, arg< double, int >( "a" ) );
    mijnVar.emit( "a", 30 );
    mijnVar.emit( "a", 30.0 );
}

30
30

Tijdens de connectie geef je aan welke types geprobeerd mogen worden, als je een type opgeeft dat zich niet laat casten naar het echte argument type, dan compiled het niet.

Wat ik hierbij vervelend vind is het feit dat je verplicht bent het verwachtte type als eerste parameter mee te geven, momenteel ben ik nog aan het zoeken naar een manier waarop ik dit kan voorkomen.

oprecht vertrouwen wordt nooit geschaad


  • cpt.spiff
  • Registratie: Augustus 2009
  • Laatst online: 21-04 23:18
Begrijp ik het goed, dat je het type van de argumenten niet in het signal-type wilt hebben (zoals boost.signals dat wel doet)? Zoals MSalters al opmerkt worden dingen makkelijker als het signal type de argumenttypes bevat (alhoewel dat inderdaad niet het default-argument voor je kan invullen).

Over het perfect forwarding issue: veel libraries die ik ken (boost.bind) "lossen dat op" door argumenten altijd by value te accepteren en van de caller te verlangen dat ze expliciet een ref (boost::ref(...)) meegeven als ze reference-gedrag willen. Het is een keuze...

Het is wel mogelijk om op regel 12 een automatische conversie te maken--zonder expliciet de types te noemen--wanneer je conversie van een beperkte set van types wilt toestaan (b.v. de builtin-types). Dit werkt zelfs wanneer sommige types helemaal niet converteerbaar zijn naar je argumenttype (dat zou dan b.v. kunnen leiden tot een runtime-fout wanneer emit wordt aangeroepen). In het algemeen is het niet mogelijk voor alle types omdat je nu eenmaal op het moment van connect niet de volledige (mogelijk oneindige) lijst van types-die-naar-type-T-convertible-zijn kunt deduceren.
Pagina: 1


Tweakers maakt gebruik van cookies

Tweakers plaatst functionele en analytische cookies voor het functioneren van de website en het verbeteren van de website-ervaring. Deze cookies zijn noodzakelijk. Om op Tweakers relevantere advertenties te tonen en om ingesloten content van derden te tonen (bijvoorbeeld video's), vragen we je toestemming. Via ingesloten content kunnen derde partijen diensten leveren en verbeteren, bezoekersstatistieken bijhouden, gepersonaliseerde content tonen, gerichte advertenties tonen en gebruikersprofielen opbouwen. Hiervoor worden apparaatgegevens, IP-adres, geolocatie en surfgedrag vastgelegd.

Meer informatie vind je in ons cookiebeleid.

Sluiten

Toestemming beheren

Hieronder kun je per doeleinde of partij toestemming geven of intrekken. Meer informatie vind je in ons cookiebeleid.

Functioneel en analytisch

Deze cookies zijn noodzakelijk voor het functioneren van de website en het verbeteren van de website-ervaring. Klik op het informatie-icoon voor meer informatie. Meer details

janee

    Relevantere advertenties

    Dit beperkt het aantal keer dat dezelfde advertentie getoond wordt (frequency capping) en maakt het mogelijk om binnen Tweakers contextuele advertenties te tonen op basis van pagina's die je hebt bezocht. Meer details

    Tweakers genereert een willekeurige unieke code als identifier. Deze data wordt niet gedeeld met adverteerders of andere derde partijen en je kunt niet buiten Tweakers gevolgd worden. Indien je bent ingelogd, wordt deze identifier gekoppeld aan je account. Indien je niet bent ingelogd, wordt deze identifier gekoppeld aan je sessie die maximaal 4 maanden actief blijft. Je kunt deze toestemming te allen tijde intrekken.

    Ingesloten content van derden

    Deze cookies kunnen door derde partijen geplaatst worden via ingesloten content. Klik op het informatie-icoon voor meer informatie over de verwerkingsdoeleinden. Meer details

    janee