1

Hello tout le monde!
J'ai voulu faire un template en C++, let's say une gestion de tilemaps génériques, donc j'ai un fichier .h avec un truc du style:
// System (hardware) tileset
template<unsigned tileSize>
class Systileset
{
private:
	u16 size, depth;
	void *tilData;
public:
	Systileset(u16 size, u16 depth, void *tilData = NULL);
	void init(u16 size, u16 depth, void *tilData = NULL);
};

Maintenant j'ai un fichier .cpp correspondant:
template<unsigned tileSize>
Systileset<tileSize>:: Systileset(u16 size, u16 depth, void *tilData)
{
	this->init(size, depth, tilData);
}

template<unsigned tileSize>
void Systileset<tileSize>::init(u16 size, u16 depth, void *tilData)
{
	this->size = size;
	this->depth = depth;
	this->tilData = tilData;
}

Tout va bien. Maintenant dans un fichier je veux l'instancier, donc je fais:
Systileset<8> bgTileset(512, 8, NULL);
Mais là je me choppe une erreur du linker:
error LNK2019: unresolved external symbol "public: __thiscall Systileset<8>:: Systileset<8>(unsigned short,unsigned short,void *)"
(??0?$Systileset@$07@@QAE@GGPAX@Z) referenced in function "void __cdecl Screen::`dynamic initializer for 'bgTileset''(void)"
(??__EbgTileset@Screen@@YAXXZ)

WTF? On notera qu'il ne gueule pas pour le init qui est appelé depuis le constructeur. Finalement qu'est-ce qui se passe, quand il compile mon systileset.cpp, est-ce qu'il le fait plusieurs fois pour les valeurs génériques avec lesquelles je l'utilise, ou est-ce qu'il ne le fait qu'une fois (comme on dirait que c'est le cas), mais avec quelle valeur dans ce cas?
J'ai eu le même problème précédemment avec des entrées génériques. J'ai voulu faire une librairie qui supportait des entrées genre:
template<typename T> T lireValeur(const char *invite, T min, T max);
Je pouvais faire appel à un lireValeur générique depuis le .cpp qui définissait le corps de la fonction, mais pas depuis ailleurs (même erreur du linker).
Merci d'avance à quiconque peut m'aider smile
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

2

Je suis pas expert C++, mais je crois que tous les template doivent être dans ton .h. Y compris le corps des fonctions.

3

Hum mais dans ce cas je suis obligé de les déclarer inline? sad
Sinon j'ai des erreurs "multiple definition of ..." même avec un #ifndef _FICHIER_H / #pragma once sorry
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

4

Ecrire un template ne génère aucun code, c'est au moment où tu vas le spécialiser pour un(e) (ensemble de) type(s)/valeur(s) donné(e)(s) (vive les parenthèses) que le code correspondant va être généré et compilé. Ce fonctionnement implique que tu dois mettre les templates dans des headers : si tu les mets dans un fichier C aucun code ne sera généré puisque c'est comme si tu ne les utilisais jamais. Et comme c'est maaaal de mettre du code dans des headers, la "norme" pour se donner bonne conscience est de créer des fichiers .hxx contenant le code de tes templates, et d'inclure ces fichiers .hxx à la fin des headers "normaux" happy

(une petite recherche google sur "hxx" devrait donner toutes les explications dont tu as besoin)
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

5

Tu dois en effet mettre tout ton template dans un .h (ou .hpp).
La raison est assez simple, le compilateur ne peut pas générer le code tant que tu ne l'instancies pas.

Bon puisque je suis sympa je te donne aussi une limitation (il y en a d'autreswink. Une méthode template ne peut pas être virtuelle wink)

6

Tiens je ne connaissais pas cette convention smile
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

7

Disons que c'est une fausse solution...

8

Oui, c'est le seul moyen.

Le standard ISO C++ prévoit un moyen d'exporter un template pour que ce que tu fais dans ton post de départ fonctionne (il faut le déclarer explicitement avec export), mais malheureusement peu de compilateurs C++ le gèrent (en particulier, g++ ne le gère pas et à ma connaissance VC++ non plus).
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

9

y'a que commeau a ma connaissance qui le gere.

10

Le problème avec les templates, c'est la gestion de la duplication de code|données:
$ cat *.*pp          
#include "File1.hpp"
#include "Template1.hpp"

void File1::func1(){
      Template1<int> titi1;
      titi1.add(1, 2);

}

#ifndef FILE1_HPP
#define FILE1_HPP

class File1{
      public:
        void func1();
};

#endif
#include "File2.hpp"
#include "Template1.hpp"

void File2::func2(){
      Template1<int> titi2;
      titi2.add(1, 2);

}

#ifndef FILE2_HPP
#define FILE2_HPP

class File2{
      public:
        void func2();
};

#endif
#ifndef TEMPLATE1_HPP
#define TEMPLATE1_HPP

template <typename T>
class Template1{
      public:
        T add(T a, T b){
                volatile int keep;
                keep = *((int*)"HER\n");
                return a + b;
        }
};

#endif
#include "File1.hpp"
#include "File2.hpp"

int main(int nArg, char **pArg){
      File1 f1;
      File2 f2;

      f1.func1();
      f2.func2();

      return 0;
}
$ gcc -S -fno-default-inline  *.cpp
$ cat *.s    

	.file	"File1.cpp"
	.text
	.align 2
.globl __ZN5File15func1Ev
	.def	__ZN5File15func1Ev;	.scl	2;	.type	32;	.endef
__ZN5File15func1Ev:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$24, %esp
	movl	$2, 8(%esp)
	movl	$1, 4(%esp)
	leal	-1(%ebp), %eax
	movl	%eax, (%esp)
	call	__ZN9Template1IiE3addEii
	leave
	ret
LC0:
	.ascii "HER\12\0"
	.section	.text$_ZN9Template1IiE3addEii,"x"
	.linkonce discard
	.align 2
.globl __ZN9Template1IiE3addEii
	.def	__ZN9Template1IiE3addEii;	.scl	2;	.type	32;	.endef
__ZN9Template1IiE3addEii:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$4, %esp
	movl	LC0, %eax
	movl	%eax, -4(%ebp)
	movl	16(%ebp), %eax
	addl	12(%ebp), %eax
	leave
	ret
	.def	__ZN9Template1IiE3addEii;	.scl	3;	.type	32;	.endef
	.file	"File2.cpp"
	.text
	.align 2
.globl __ZN5File25func2Ev
	.def	__ZN5File25func2Ev;	.scl	2;	.type	32;	.endef
__ZN5File25func2Ev:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$24, %esp
	movl	$2, 8(%esp)
	movl	$1, 4(%esp)
	leal	-1(%ebp), %eax
	movl	%eax, (%esp)
	call	__ZN9Template1IiE3addEii
	leave
	ret
LC0:
	.ascii "HER\12\0"
	.section	.text$_ZN9Template1IiE3addEii,"x"
	.linkonce discard
	.align 2
.globl __ZN9Template1IiE3addEii
	.def	__ZN9Template1IiE3addEii;	.scl	2;	.type	32;	.endef
__ZN9Template1IiE3addEii:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$4, %esp
	movl	LC0, %eax
	movl	%eax, -4(%ebp)
	movl	16(%ebp), %eax
	addl	12(%ebp), %eax
	leave
	ret
	.def	__ZN9Template1IiE3addEii;	.scl	3;	.type	32;	.endef
	.file	"main.cpp"
	.def	___main;	.scl	2;	.type	32;	.endef
	.text
	.align 2
.globl _main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$24, %esp
	andl	$-16, %esp
	movl	$0, %eax
	movl	%eax, -8(%ebp)
	movl	-8(%ebp), %eax
	call	__alloca
	call	___main
	leal	-1(%ebp), %eax
	movl	%eax, (%esp)
	call	__ZN5File15func1Ev
	leal	-2(%ebp), %eax
	movl	%eax, (%esp)
	call	__ZN5File25func2Ev
	movl	$0, %eax
	leave
	ret
	.def	__ZN5File25func2Ev;	.scl	3;	.type	32;	.endef
	.def	__ZN5File15func1Ev;	.scl	3;	.type	32;	.endef
	.file	"File1.cpp"
	.text
	.align 2
.globl __ZN5File15func1Ev
	.def	__ZN5File15func1Ev;	.scl	2;	.type	32;	.endef
__ZN5File15func1Ev:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$24, %esp
	movl	$2, 8(%esp)
	movl	$1, 4(%esp)
	leal	-1(%ebp), %eax
	movl	%eax, (%esp)
	call	__ZN9Template1IiE3addEii
	leave
	ret
LC0:
	.ascii "HER\12\0"
	.section	.text$_ZN9Template1IiE3addEii,"x"
	.linkonce discard
	.align 2
.globl __ZN9Template1IiE3addEii
	.def	__ZN9Template1IiE3addEii;	.scl	2;	.type	32;	.endef
__ZN9Template1IiE3addEii:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$4, %esp
	movl	LC0, %eax
	movl	%eax, -4(%ebp)
	movl	16(%ebp), %eax
	addl	12(%ebp), %eax
	leave
	ret
	.def	__ZN9Template1IiE3addEii;	.scl	3;	.type	32;	.endef
	.file	"File2.cpp"
	.text
	.align 2
.globl __ZN5File25func2Ev
	.def	__ZN5File25func2Ev;	.scl	2;	.type	32;	.endef
__ZN5File25func2Ev:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$24, %esp
	movl	$2, 8(%esp)
	movl	$1, 4(%esp)
	leal	-1(%ebp), %eax
	movl	%eax, (%esp)
	call	__ZN9Template1IiE3addEii
	leave
	ret
LC0:
	.ascii "HER\12\0"
	.section	.text$_ZN9Template1IiE3addEii,"x"
	.linkonce discard
	.align 2
.globl __ZN9Template1IiE3addEii
	.def	__ZN9Template1IiE3addEii;	.scl	2;	.type	32;	.endef
__ZN9Template1IiE3addEii:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$4, %esp
	movl	LC0, %eax
	movl	%eax, -4(%ebp)
	movl	16(%ebp), %eax
	addl	12(%ebp), %eax
	leave
	ret
	.def	__ZN9Template1IiE3addEii;	.scl	3;	.type	32;	.endef
	.file	"main.cpp"
	.def	___main;	.scl	2;	.type	32;	.endef
	.text
	.align 2
.globl _main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$24, %esp
	andl	$-16, %esp
	movl	$0, %eax
	movl	%eax, -8(%ebp)
	movl	-8(%ebp), %eax
	call	__alloca
	call	___main
	leal	-1(%ebp), %eax
	movl	%eax, (%esp)
	call	__ZN5File15func1Ev
	leal	-2(%ebp), %eax
	movl	%eax, (%esp)
	call	__ZN5File25func2Ev
	movl	$0, %eax
	leave
	ret
	.def	__ZN5File25func2Ev;	.scl	3;	.type	32;	.endef
	.def	__ZN5File15func1Ev;	.scl	3;	.type	32;	.endef
$ gcc -fno-default-inline  *.cpp
$ grep -uc HER a.exe 
2

11

comment c'est trop imbitable le C++ assemblé trilove

12

En même temps, c'est le but de templates : pouvoir générer du code facilement. Donc forcément du code quasi-redondant...
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

13

Je pense que tu n'as pas tout a fait saisis le danger de la chose:
$ cat *.cpp                        
#include "File1.hpp"

template <typename T>
class Template1{
      public:
        T add(T a, T b){
                asm volatile ("ltr 1");
                return a + b;
        }
};



void File1::func1(){
      Template1<int> titi1;
      titi1.add(1, 2);

}

#include "File2.hpp"

template <typename T>
class Template1{
      public:
        T add(T a, T b){
                asm volatile ("ltr 2");
                return a + b;
        }
};

void File2::func2(){
      Template1<int> titi2;
      titi2.add(1, 2);

}

#include "File1.hpp"
#include "File2.hpp"
#include "Template1.hpp"

int main(int nArg, char **pArg){
      File1 f1;
      File2 f2;

      f1.func1();
      f2.func2();
      Template1<int> tata;
      tata.add(0,0);

      return 0;
}
$ gcc -fno-default-inline  *.cpp -c
$ gcc  File2.o File1.o main.o && objdump.exe -d a.exe | grep ltr
  401793:       0f 00 1d 02 00 00 00    ltr    0x2
$ gcc  File1.o File2.o main.o && objdump.exe -d a.exe | grep ltr
  401793:       0f 00 1d 01 00 00 00    ltr    0x1
$ gcc  main.o File1.o File2.o && objdump.exe -d a.exe | grep ltr
  401793:       0f 00 1d 00 00 00 00    ltr    0x0
$ objdump.exe -d a.exe | grep -c ltr
1


Ceci est un comportement normal qui n'est pas spécifique aux templates, mais il en résulte que sa spécialisation ne peut se faire que dans son .hpp ou .cxx sous peine d'avoir un comportement qui varie en fonction du compilateur ou de l'ordre du link. (en ada au moins les types génériques sont plus propres car on fait un .o par type de spécialisation.)
En gros gcc optimise la duplication de code pour avoir un seul code mais n'est pas capable d'optimiser la duplication des sections (on a des sections non utilisées). Le compilateur texas lui enregistre toutes les spécialisations qu'il doit faire d'un template et ensuite construit ce template -> c'est bien plus propre.

En en revient au fait que le C, C++ ne sont pas des langages qui ont été spécifiés contrairement à l'ada, et par conséquent on a des comportements différents en fonction des compilateurs...

14

Bon pour que ce soit plus parlant:
$ cat *.cpp                        
#include "File1.hpp"
#include "Template1.hpp"

template <>
class Template1<int>{
      public:
        int Template1::add(int a, int b){
                asm volatile ("ltr 1");
                return a + b;
        }
};



void File1::func1(){
      Template1<int> titi1;
      titi1.add(1, 2);

}

#include "File2.hpp"
#include "Template1.hpp"


void File2::func2(){
      Template1<int> titi2;
      titi2.add(1, 2);

}

#include "File1.hpp"
#include "File2.hpp"
#include "Template1.hpp"

int main(int nArg, char **pArg){
      File1 f1;
      File2 f2;

      f1.func1();
      f2.func2();
      Template1<int> tata;
      tata.add(0,0);

      return 0;
}

et biensur:
$ gcc  main.o File1.o File2.o && objdump.exe -d a.exe | grep ltr
  401793:       0f 00 1d 00 00 00 00    ltr    0x0
$ gcc  File2.o main.o File1.o && objdump.exe -d a.exe | grep ltr
  401793:       0f 00 1d 00 00 00 00    ltr    0x0
$ gcc  File1.o main.o File2.o && objdump.exe -d a.exe | grep ltr
  401793:       0f 00 1d 01 00 00 00    ltr    0x1

15

en même temps c'est évident... pas de quoi défoncer la mise en page en postant 50ko de code inutile, en tout cas.
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

16

Non ce n'est pas évident car non normalisé. Tu n'as pas du tout ce même comportement avec d'autres compilateurs...
Mais bon si tu es capable sa savoir a la tete du compilateur si tu as soit duplication du code soit duplicatin de la section soit choix alléatoire de la fonction soit un comportement propre... alors tu es très fort wink

17

Ton code n'est pas conforme au standard, il ne respecte pas l'ODR (One Definition Rule), donc forcément le compilateur peut en faire n'importe quoi.
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

18

Seulement dans le deuxième exemple

19

Tous tes exemples vont à l'encontre de l'ODR.
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

20

connais tu une manière de generer un unique .o pour un template spécialisé autre que export (qui lui permet de le garder generic)? Peu importe si je dois faire un fichier par spécialisation, je préfère cela à la merde que me sort le compilo.

21

oui, tu prends une souris verte qui courait dans l'herbe, tu l'attrapes par la queue et tu la montres a ces messieurs.

22

pencil

23

Ok merci bien les gars smile
J'ai pas encore essayé les .hxx mais par contre j'ai vu que je pouvais spécialiser en faisant dans le .cpp qui les définit:
// System tilesets can be used for 8x8 and 16x16 tiles
template Systileset<8>;
template Systileset<16>;

C'est ce qu'il me fallait, même si c'est plus vraiment de la généricité au sens Ada du terme justement ^^
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741