Toon posts:

[C++] Template params afleiden van ctor argumenten

Pagina: 1
Acties:

Verwijderd

Topicstarter
Weer eens even een echte C++ topic (dus niet zo'n C++ topic waar iedereen alleen maar over C lult :)), over het afleiden van template parameters uit function call argumenten en zo.

Zoals jullie allemaal weten, is het mogelijk om template parameters af te laten leiden van de argumenten die je aan een template functie geeft:
code:
1
2
3
4
5
6
7
8
template <class C>
void f (C) {}

int main ()
{
    int i;
    f (i);
}

In main wordt impliciet f<int>(i) aangeroepen, en dit kan je bij de wat complexere templates heel wat typwerk besparen.

Wat mij nou wel handig leek, is als we dit zelfde foefje ook voor template classes konden gebruiken; dus de template parameters laten afleiden uit de constructor parameters:
code:
1
2
3
4
5
6
7
8
9
10
11
12
template <class C>
struct s
{
    s (C)
    {}
};

int main ()
{
    int i;
    s o (i);
}

In deze code zou o dus een object worden van het type s<int>.

Nou las ik in mijn C++ boek (TC++PL), dat dit niet kan. De reden zou zijn dat het een zooitje zou worden aangezien je meerdere constructors etc kan hebben in een class. (ik heb mn boek er hier niet bij dus ik kan helaas ff niet citeren (doe ik wel ff als ik thuis ben).) Ik snap hier echter niks van, want normale functions kun je net zo goed overloaden.
Iemand op irc vertelde me dat de compiler in sommige gevallen niet zou weten wat ie ervan moest maken, maar ook dit is net zo goed het geval bij template functions.

Dus mijn vraag is nog steeds: wat is nou de echte reden dat dit niet kan ? (niet dat Stroustrop me loopt te besodemieteren, ik begrijp alleen zijn uitleg niet..)

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 20:00

.oisyn

Moderator Devschuur®

Demotivational Speaker

nou volgens mij loopt Stroustrop je idd te besodemieteren ;)

maar nu even serieus... werkt dat niet??? Goh. Even de C++ annotations erbij pakken...

.edit: hmmmm wacht, nu begrijp ik je pas
da's logisch dat het niet werkt, je moet als je een template class instantieert altijd de template parameters erbij zetten (tenzij ze default zijn natuurlijk)

want wat als je nou dit hebt:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename T> class C
{
public:
    C (int);
    C (T);
};

int main ()
{
    int i = 5;
    C c (i); // moet ie hier nou C (int) of C (T = int) aanroepen?
    // en als het C (int) is, wat is dan T?
}

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.


Verwijderd

Topicstarter
Das toch precies hetzelfde als dit:
code:
1
2
3
4
5
6
7
8
template <class C> void s (int) {}
template <class C> void s (C) {}

int main ()
{
    int i = 5;
    s (i); // moet ie hier nou een s (int) of s (C = int) aanroepen ?
}

? En dat werkt gewoon..

En verder zou de compiler natuurlijk gewoon kunnen gaan klagen als ie er niet uitkomt (bijvoorbeeld bij jouw C(int) constructor). In zo'n geval zou je dan dus ff expliciet met <>'s de template parameters moeten geven. Geen probleem toch ?

.edit:
Probeer maar eens een C<int> c (3); van jouw class c te maken, dan krijg je ook ambiguity errors omdat de constructors dan hetzelfde prototype hebben.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 20:00

.oisyn

Moderator Devschuur®

Demotivational Speaker

das waar, maar ik heb c++ dan ook niet uitgevonden :)

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.


Verwijderd

Wat jij wilt is bepalen welk object er geconstrueerd wordt aan de hand van het type van de constructor parameters? Dan heb je toch niet veel aan templating op deze manier.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <class C>
struct s
{
    s (C)
    {}
};

int main ()
{
    int i;
    s o (i); // werkt niet, je moet een template parameter opgeven
    s<int> o(i);      // dit werkt
    s<typename i> o(i); // dit werkt ook
}

Met default template argumenten:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <class C = int>
struct s
{
    s (C)
    {}
};

int main ()
{
    int i;
    float f;
    s<> o(i); // dit kan
    s<> o(f); // dit niet, er wordt een s<int> aangemaakt
}

Verwijderd

Topicstarter
Mietje:
Je geeft mooie stukken code, maar ik heb er niet veel aan zonder wat uitleg.. :?

Verwijderd

Op maandag 17 september 2001 21:37 schreef Sneech het volgende:
Mietje:
Je geeft mooie stukken code, maar ik heb er niet veel aan zonder wat uitleg.. :?
Wat ik wil laten zien is dat het templaten van classes anders werkt dan het templaten van functies.

In m'n eerste stukje code laat ik zien hoe je correct een templated structure s declareert. Als je daarover nadenkt, zie je dat je nooit dmv. templating van classes/structures kunt bereiken wat jij wilt: de template moet het type van zijn parameters kennen voordat er een constructor voor die templated class gegenereerd wordt.

In m'n tweede stukje code laat ik zie hoe je een default parameter int aan die templated structure s toevoegd, en hoe je zo'n templated structure met default parameters declareert (met lege <> haken). Vervolgens laat ik je zien dat ook dat je niet uit de brand helpt.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Dit is je baseclass.
struct s
{
};

// Dit is jouw getemplate class, een afgeleide van baseclass s.
template <class T>
struct deriv_s : public s
{
    deriv_s(T) {}
};

// Deze functie genereert dynamisch een nieuw deriv_s object,
// afhankelijk van het type van de parameter.
template <class T>
s* new_s(T blah)
{
    return new deriv_s<T>(blah);
}

Ik zou zo snel geen andere manier weten om automatisch een dynamisch type te genereren.

Verwijderd

Topicstarter
code:
1
2
3
4
5
6
7
8
9
10
11
template <class C>
struct s
{
    s (C)
    {}
};
int main ()
{
    int i;
    s o (i);
}

Ik zal even denkbeeldig de statement s o (i); parsen, zoals ik me voorstel hoe die class template parameter deduction in z'n werk zou gaan:

er wordt geprobeerd een object o van het type s<?> aan te maken
template parameters nodig
geen <>'s aanwezig
probeer parameters af te leiden uit constructor argumenten
vind passende constructor
s::s(C), is deze toepasbaar ?
ja, want als je voor C het type van i (int) invult, past ie precies, en is tevens de template parameter C gevonden. een perfect match dus
-----------------------------------------------------------------
Eindresultaat: o is van het type s<int>, en de gebruikte constructor is dus s::s(C).

Waar ga ik de mist in ? Is dit niet realiseerbaar ?

Verwijderd

Topicstarter
Misschien is het niet eens zo moeilijk om zelf ff een proggy te schrijven dat deze class template parameter deduction uit constructor arguments parsed.

En dan ff als patch voor gcc schrijven, hebben we meteen weer een mooie GNU C++ extension erbij >:)

Verwijderd

Op maandag 17 september 2001 23:23 schreef Sneech het volgende:
Misschien is het niet eens zo moeilijk om zelf ff een proggy te schrijven dat deze class template parameter deduction uit constructor arguments parsed.

En dan ff als patch voor gcc schrijven, hebben we meteen weer een mooie GNU C++ extension erbij >:)
Het is binnen de huidige taalstandaard gewoon niet mogelijk, er zouden name clashes kunnen ontstaan met niet getemplate classes/structures.

Als je dit nodig hebt om als het ware achteraf een virtual baseclass (interface in java) over bestaande classes/structs te "gooien" dan bestaat er al een G++ extensie signature:
code:
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
struct A {
  bool write(ostream&) {}
  bool read(istream&) {}
};

struct B {
  bool write(ostream&) {}
  bool read(istream&) {}
};

// Signature definieert gemeenschappelijke methodes, maar buiten
// de overervingshierarchie om.
signature IO {
  bool write(ostream&);
  bool read(istream&);
};

// Access tot de methodes via signature pointers.
bool write(IO* sp, ostream& s) { return sp->write(s); }
bool read(IO* sp, istream& s) { return sp->read(s); }

int main () {
  A a;
  B b;
  write(&a,cout);
  read(&b,cin);
}

Verwijderd

Topicstarter
Op dinsdag 18 september 2001 01:41 schreef mietje het volgende:
Het is binnen de huidige taalstandaard gewoon niet mogelijk, er zouden name clashes kunnen ontstaan met niet getemplate classes/structures.
Das natuurlijk weer een uitleg van niks (no offence).. Heb je hier een voorbeeld van ? Wat is je reactie op mijn denkbeeldige statement parse ?
Als je dit nodig hebt om als het ware achteraf een virtual baseclass (interface in java) over bestaande classes/structs te "gooien" ...
Nee, en laten we on-topic blijven.

Verwijderd

Op maandag 17 september 2001 23:23 schreef Sneech het volgende:
Misschien is het niet eens zo moeilijk om zelf ff een proggy te schrijven dat deze class template parameter deduction uit constructor arguments parsed.
Je denkbeeldige parsing lijkt me wel correct, ik zie alleen het nut er niet van. Templates zijn een HULPmiddel, om typewerk te voorkomen. Verder niet. Aangezien templates, omdat ze met types werken, ambigue constructies kunnen construeren, moet je dat zien te voorkomen. Je kunt wel piepen dat de taal het niet doet en dat je dat maar aan moet passen, maar dat is de zaak omdraaien. Beter is te gaan kijken hoe je ZONDER ambiguiteit te creeeren, TOCH je spullen in nette (!) templates kunt onderbrengen. En wellicht vergt dat de definitie van 2 templates ipv 1.

Verwijderd

Op dinsdag 18 september 2001 02:28 schreef Sneech het volgende:
Das natuurlijk weer een uitleg van niks (no offence).. Heb je hier een voorbeeld van ? Wat is je reactie op mijn denkbeeldige statement parse ?
Een templated class is geen baseclass, elke instantie van de template is een aparte class die niets met andere instanties van die template te doen heeft. In ons voorbeeld heeft een s<int> dus niets gemeen met een s<float>, en we kunnen s niet zonder <> haken (als een baseclass) aanspreken. Het type s zonder <> haken bestaat eenvoudigweg niet.

Maw. het moet van te voren bekend zijn van welk type de instantiatie van de template is, voordat er constructors voor die instantiatie door de compiler gegenereerd worden. Andersom is onmogelijk.
Nee, en laten we on-topic blijven.
Ik zag in een ander recent topic dat je iets dergelijks probeerde, ik nam aan dat dit nog steeds het probleem was.

Verwijderd

Topicstarter
Ik zal even concreet zijn, hier ff een voorbeeld structje:
code:
1
2
3
4
5
template <class A, class B, class C, class D> struct s
{
    s (A (B::*) (C, D))
    {}
};

Zo, het enige wat ik wilde is om dit:
code:
1
    s<ostream &, ostream, const char *, long> o (ostream::write);

Te kunnen schrijven als dit:
code:
1
s o (ostream::write);

Aangezien ie uit dat constructor argument alle template parameters zou kunnen vissen.

Puur een typwerk besparing dus, verder wil ik niets ingewikkelds ofzo..

Verwijderd

No offence, maar waarom maak je het de lezer van je code zo nodeloos ingewikkeld? C++ kent vele zaken die krachtig kunnen zijn, maar voor jehet weet zit je tegen een (onbedoeld) stukje obfuscated C++ aan te kijken. Wat wil je precies bereiken met je code? Wellicht is er een veel betere en elegantere oplossing voorhanden.

Verwijderd

Op dinsdag 18 september 2001 11:31 schreef Sneech het volgende:
Te kunnen schrijven als dit:
code:
1
s o (ostream::write);

Aangezien ie uit dat constructor argument alle template parameters zou kunnen vissen.

Puur een typwerk besparing dus, verder wil ik niets ingewikkelds ofzo..
Dit kan dus gewoon niet, de compiler kan geen type-parameters uit de constructor vissen. De compiler moet eerst de type-parameters kennen, voordat er überhaupt een constructor gegenereerd wordt. De compiler zal s o (ostream::write); dan ook zien als een declaratie van een ongetemplate type s.

Overigens kan deze constructie sowieso niet werken, je geeft namelijk method-pointers door zonder een object-pointer. De s<> structs weten dus weliswaar welke methode (write) ze moeten toepassen, maar niet op welk object.

Verwijderd

Topicstarter
mietje:

1. Ik WEET dat het niet kan zoals C++ nu is, dat heb ik in de allereerste post in deze thread laten weten.

2. Waarom vertel je me nou niet gewoon ff waarom de realisatie zoals ik die in mijn denkbeeldige parse liet zien onmogelijk is ? Verder lijkt het "probleem" wat jij beschrijft zich net zo goed voor te doen bij overloaded template functions (en aangezien die prima werken, is dat dus geen probleem).

Verwijderd

Ok, dan een stukje (foute) voorbeeldcode:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Base {};

struct A : public Base {};

struct B : public Base {};

template <class T>
struct s {
  s(const T& obj) {}
};

void doe_iets(const Base& obj) {
  s o(obj);  // Welke template wordt nu geinstantieerd?
         // s<Base>, s<A> of s<B> ?
}

int main() {
  A a;
  B b;
  doe_iets(a);
  doe_iets(b);
  return 0;
}

Zoals je ziet aan de functie doe_iets(), zou de compiler pas in runtime kunnen bepalen van welk type de template s is. Dan kan de compiler natuurlijk niet al in compile-time de juiste methods genereren.

Verwijderd

Topicstarter
mietje:

Op zich een interessante kwestie, maar het feit dat de compiler deze wat templates betreft equivalente code:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Base {};
struct A : public Base {};
struct B : public Base {};

template <class T> void f (const T& obj) {}

void doe_iets(const Base& obj)
{
    f (obj); // Welke template wordt nu gecalled ? f<Base>, f<A> of f<B> ?
}

int main ()
{
    A a;
    B b;
    doe_iets(a);
    doe_iets(b);
    return 0;
}

zonder problemen compiled geeft aan dat dat geen probleem is.

Verwijderd

Op dinsdag 18 september 2001 13:59 schreef Sneech het volgende:
mietje:

Op zich een interessante kwestie, maar het feit dat de compiler deze wat templates betreft equivalente code: <snip>

zonder problemen compiled geeft aan dat dat geen probleem is.
Zoals ik al opmerkte is een functie template iets anders dan een class template. Voor een functie template hoeft de compiler geen nieuw datatype aan te maken, voor een class template wel.

In jouw functie doe_iets() wordt dus via de virtuele methods van Base dmv. runtime binding de juiste method aangeroepen. Met class templates is dit niet mogelijk, omdat het type al compile-time bekend moet zijn (en dat is zo bij elke strongly-typed taal). De compiler kan gewoon geen methods runtime binden, als het type waaraan die methods gebonden moeten worden niet bestaat.

<edit>Warrige zin</edit>

Verwijderd

Topicstarter
Op dinsdag 18 september 2001 14:15 schreef mietje het volgende:
In jouw functie doe_iets() wordt dus via de virtuele methods van Base dmv. runtime binding de juiste method aangeroepen.
Nee dus. Als we even wat extra cout-code toevoegen, zien we dat mijn doe_iets() niet automatisch een f aanroept met als template parameter de derived class:
code:
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
#include <ostream>
using std::cout;

struct Base     { void g () const { cout << "base::g\n"; } };
struct A : public Base { void g () const { cout << "A::g\n";    } };
struct B : public Base { void g () const { cout << "B::g\n";    } };

template <class T> void f (const T& obj)
{
    obj.g();
}

void doe_iets(const Base& obj)
{
    f (obj); // er vindt geen runtime binding plaats, het wordt gewoon f<Base> (zie output)
}

int main ()
{
    Base a;
    A b;
    B c;
    doe_iets(a);
    doe_iets(b);
    doe_iets(c);
    return 0;
}

Dit geeft als output:
base::g
base::g
base::g
Geen runtime binding dus :'(

.edit: typo

Verwijderd

Op dinsdag 18 september 2001 14:30 schreef Sneech het volgende:
struct Base { void g () const { cout << "base::g\n"; } };
:)

probeer eens:

struct Base { virtual void g () const { cout << "base::g\n"; } };

;)

<edit>
Maw. bij een functie template maakt het niets uit of er een referentie naar een baseclass of een derived object wordt doorgegeven, de virtuals die in de baseclass gedeclareerd zijn bepalen de functionaliteit. Dit is een groot verschil met een class template, waar de template parameter de functionaliteit bepaalt.
</edit>

Verwijderd

Topicstarter
Op dinsdag 18 september 2001 14:32 schreef mietje het volgende:
Maw. bij een functie template maakt het niets uit of er een referentie naar een baseclass of een derived object wordt doorgegeven, de virtuals die in de baseclass gedeclareerd zijn bepalen de functionaliteit.
Dit is natuurlijk onzin :P.
Er bestaat ook nog zoiets als non-virtual functions (en data member bestaan zelfs ook nog). Als je vanuit de template function een non-virtual member aanroept, is dat bij de base class gewoon een andere dan bij de derived class.
Maakt dus wel degelijk iets uit.

Verwijderd

Op dinsdag 18 september 2001 14:53 schreef Sneech het volgende:
Dit is natuurlijk onzin :P.
Er bestaat ook nog zoiets als non-virtual functions, en data members bestaan ook nog. Dus als er vanuit de template function een non-virtual member wordt aangeroepen, is dat gewoon een andere functie.
Klopt, voor non-virtuals maakt het verschil. Het probleem bij templated classes is vergelijkbaar: je hebt geen mogelijkheid om op eoa. manier iets virtual tov. de templated class te definieeren. Een templated class is dus geen baseclass maar een hele verzameling baseclasses, en er is geen extra runtime binding mogelijk om te bekijken welke baseclass uit die verzameling de juiste is.

Verwijderd

Topicstarter
Hoe zit het eigenlijk met Curry ? Die noemt zichzelf toch C++ vraagbaak ?

Curry, wat denk jij hier zo'n beetje van ?

Verwijderd

Topicstarter
Het argument dat het voor een template function niets uit zou maken of zn template argument nou een base class of een derived class is, en dat ie het zich daardoor kan veroorloven deze template parameters uit de function parameters te halen, is dus gewoon niet waar.
Verder blijkt uit mijn experimentje dat er bij het aanroepen van een template functie zonder expliciet template parameters te geven, geen runtime binding plaats vindt.

Volgende argument :9?

Verwijderd

Op dinsdag 18 september 2001 16:36 schreef Sneech het volgende:
Het argument dat het voor een template function niets uit zou maken of zn template argument nou een base class of een derived class is, en dat ie het zich daardoor kan veroorloven deze template parameters uit de function parameters te halen, is dus gewoon niet waar.
/me zucht :)

Een functie template is geen class template. In een functie template is het functieargument de template-parameter; in een class template kan het constructorargument geen template-parameter zijn, omdat de constructor compile-time gegenereerd moet worden, en dus de template-parameter ook compile-time bekend moet zijn.

Om het simpel te zeggen: hoe weet de compiler compile-time hoeveel memory hij voor een templated class instantie moet reserveren, als hij niet weet van welk type die instantie is. De compiler zou alle mogelijke instanties van de templated class moeten genereren (mega code-bloat), en genoeg memory moeten reserveren voor de grootst mogelijke instantie.
Verder blijkt uit mijn experimentje dat er bij het aanroepen van een template functie zonder expliciet template parameters te geven, geen runtime binding plaats vindt.
In C++ vindt nooit runtime binding plaats als je dat niet expliciet met het virtual keyword opgeeft.

Verwijderd

Topicstarter
Allemaal leuk en aardig, maar na al deze posts ben je nog steeds niet ingegaan op de uitnodiging om mijn denkbeeldige parsing eens onder de loep te nemen om te kijken welke stap onmogelijk is.
Als je kan laten zien dat ik daar iets doe wat gewoon helemaal niet realiseerbaar is, ben ik echt meteen overtuigd..

Verwijderd

Op dinsdag 18 september 2001 18:27 schreef Sneech het volgende:
Allemaal leuk en aardig, maar na al deze posts ben je nog steeds niet ingegaan op de uitnodiging om mijn denkbeeldige parsing eens onder de loep te nemen om te kijken welke stap onmogelijk is.
Als je kan laten zien dat ik daar iets doe wat gewoon helemaal niet realiseerbaar is, ben ik echt meteen overtuigd..
Je denkbeeldige parsing werkt alleen zolang de class template maar een template parameter heeft.

Parse deze eens:
code:
1
2
3
4
5
6
7
8
9
10
template <class C, class D>
struct s {
  s(C) {}
  s(D) {}
};

int main() {
  int i;
  s o (i);
}

Naast problemen met virtuals heeft de compiler hier nog een onoverkomelijk probleem: hij weet niet welke constructor hij moet kiezen, en dus ook niet welke template geinstantieerd moet worden.

Verwijderd

Topicstarter
Dus geeft ie een ambiguity error, klaar.

Verwijderd

Op dinsdag 18 september 2001 18:47 schreef Sneech het volgende:
Dus geeft ie een ambiguity error, klaar.
Correct. Maar die ambiguiteit komt dus niet doordat je iets fout doet in je definitie van je template class, maar doordat je een taalconstructie (s o(i);) hebt bedacht die niet eenduidig is.

Dit soort taalconstructies die alleen onder bepaalde voorwaarden werken, of andere delen van de taal tegenwerken (constructor overloading) zijn natuurlijk hell voor een programmeur, en maken de toch al moeilijke taal er niet makkelijker op.

Verwijderd

Topicstarter
Ik vind het heel redelijk om het afleiden van die parameters uit constructor argumenten als keuze aan te bieden, met de explicietere syntax voor de ambigu-gevallen.
En als dit een hel voor de programmeur zou zijn, zou het ook niet toegelaten zijn voor de template functies (die ook totaal andere functionaliteit kunnen hebben voor verschillende template parameters, die bij een call dus niet expliciet gespecificeerd hoeven te worden).

Verwijderd

Op woensdag 19 september 2001 10:19 schreef Sneech het volgende:
Ik vind het heel redelijk om het afleiden van die parameters uit constructor argumenten als keuze aan te bieden, met de explicietere syntax voor de ambigu-gevallen.
En als dit een hel voor de programmeur zou zijn, zou het ook niet toegelaten zijn voor de template functies (die ook totaal andere functionaliteit kunnen hebben voor verschillende template parameters, die bij een call dus niet expliciet gespecificeerd hoeven te worden).
Denk nu eens na over het verschil tussen functie templates en class templates. Bij een functie template is het altijd eenduidig welke functie er bedoeld wordt aan de hand van het type van de functieargumenten. Bij class templates is dit lang niet altijd eenduidig.

Verder heb ik nog een ander argument tegen je syntaxuitbreiding: hoe maak je syntactisch onderscheid met deze member template versie:
code:
1
2
3
4
5
6
7
8
struct s {
  template <class T> s(T) {}
};

int main() {
  int i;
  s o(i);
}

Dit is dus wel syntactisch correct volgens de standaard...

Verwijderd

Topicstarter
Onderscheid maken is niet nodig, aangezien er geen non-templated class en wel-templated class met dezelfde naam in dezelfde scope mogen bestaan :)
Pagina: 1