• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

question-closed C++, konkretyzacja szablonu:

Object Storage Arubacloud
0 głosów
728 wizyt
pytanie zadane 9 marca 2018 w C i C++ przez Jakub 0 Pasjonat (23,120 p.)
zamknięte 9 marca 2018 przez Jakub 0

Witam, w tej chwili uczę się stosunkowo nowego tematu dotyczącego szablonów funkcji i  często się mylę, mam taki kod:

#include <iostream>	

template <typename T> void mul(T a, T b);

int main() {
	
	double a = 1.5, b = 2;
	mul<double>(a, b);

}

template <typename T> void mul(T a, T b) {
	std::cout << a*b << std::endl;
}

Działa on jak najbardziej poprawnie. Tworzymy tu jawną konkretyzację podczas użycia funkcji w programie, jednak zrobiłem tez coś takiego:

#include <iostream>	

template <typename T> void mul(T a, T b);

int main() {
	template void mul<double>(double, double);
	double a = 1.5, b = 2;
	mul(a, b);
}

template <typename T> void mul(T a, T b) {
	std::cout << a*b << std::endl;
}

Przykład zrobiłem dokładnie na tej samej zasadzie co przykład w książce, umieściłem deklaracje jawnej konkretyzacji w funkcji. Mam błąd kompilacji:

jawne tworzenie instancji szablonu może nastąpić tylko w zakresie przestrzeni nazw   

 Nie  wiem co zrobiłem źle i czy w ogóle dobrze zrozumiałem temat...

Będę wdzięczny za pomoc.

komentarz zamknięcia: już znam wytłumaczenie

1 odpowiedź

+1 głos
odpowiedź 9 marca 2018 przez mokrowski Mędrzec (155,640 p.)
wybrane 9 marca 2018 przez Jakub 0
 
Najlepsza
Linijkę 6, umieść poza main(). Ta linijka jawnie tworzy instancję szablonu w pliku *.o . W kodzie który napisałeś, w zasadzie jest zbędna bo wystarcza mechanizm dopasowania szablonu.
komentarz 9 marca 2018 przez Jakub 0 Pasjonat (23,120 p.)

Dziękuje, dziwne bo w książce z której się uczę ta linijka jest w main(). Pewne jakiś kolejny błąd...  teraz mam za to takie coś:

Jestem trochę zaniepokojony podkreśleniem którym zaznaczyłem, to oznacza np że jest prototyp funkcji a nie wykryto definicji... Nie wiem jak to określić słowem...

1
komentarz 9 marca 2018 przez mokrowski Mędrzec (155,640 p.)

Linijka 

template void mul<double>(double, double);

W tej chwili w Twoim kodzie jest zbędna. Kompilator dobrze sobie poradzi bazując na dedukcji typu ...

template <typename T> void mul(T a, T b);

Linijkę o której dyskutujemy, użyjesz w bardzo jednostkowych przypadkach. Jednym z nich będzie wymuszenie tworzenia instancji funkcji szablonowej dla double w jednostce kompilacji (czyli pliku *.o który powstanie z *.cpp) jeśli będziesz miał rozdzieloną deklarację funkcji szablonowej od jej definicji. Czyli w przypadku gdy chcesz w mul.hpp mieć tylko:

template <typename T> void mul(T a, T b);

.. a w mul.cpp mieć:

template <typename T> void mul(T a, T b) {
    std::cout << a*b << std::endl;
}

Wtedy aby całość kompilowała się poprawnie, należy powiedzieć kompilatorowi by ją wygenerował poprzez zapis...

template void mul<double>(double, double);

Dołączony do pliku mul.cpp.

To jest jeden z takich przypadków gdzie ten ostatni zapis może być przydatny.

komentarz 9 marca 2018 przez Jakub 0 Pasjonat (23,120 p.)
edycja 9 marca 2018 przez Jakub 0

Wiem że trochę mam pytań ale wykorzystam już sytuacje żeby nie zadawać nowego pytania bo temat właściwie ten sam, sprawdziłem już sytuacje o której była mowa w której trzeba wykorzystać  jawną konkretyzację:

klasa.h

class klasa
{
public:
	template <typename T> void swap(T &a, T &b);
};

klasa.cpp

#include "klasa.h"

template void klasa::swap<int>(int&, int&); 
template void klasa::swap<double>(double&, double&);
template void klasa::swap<char>(char&, char&);
template void klasa::swap<short>(short&, short&);
template void klasa::swap<long double>(long double&, long double&);

template <typename T> void klasa::swap(T &a, T &b) {
	T temp = a;
	a = b;
	b = temp;
}

main.cpp

#include <iostream>
#include "klasa.h"

int main(){
	klasa kl;
	char a = 'q', b = 'e';
	std::cout << a << " " << b << std::endl;
	kl.swap(a, b);
	std::cout << a << " " << b << std::endl;

	long double x = 6, y = 8;
	std::cout << x << " " << y << std::endl;
	kl.swap(x, y);
	std::cout << x << " " << y << std::endl;

    return 0;
}

Program chodzi poprawnie i prawdą jest że bez takich deklaracji:

template void klasa::swap<int>(int&, int&); 
itd...

dla danych typów program nie kompiluje się poprawnie (jeżeli chcemy użyć metody swap() a nie mamy dla typów argumentów jawnej konkretyzacji). Tu mam takie małe pytanka (szukałem odpowiedzi w internecie ale nic):

1. Właściwie to dlaczego w takim przypadku bez jawnej konkretyzacji w pliku.cpp danej klasy kompilator sobie nie poradzi?

2. Dlaczego próba użycia jawnej konkretyzacji i jawnej specjalizacji dla tego samego typu jest błędem? Czy da to się jakoś obejść?

Bo np chcę zrobić tak (plik .cpp):

#include "klasa.h"

template void klasa::swap<int>(int&, int&); 
template void klasa::swap<double>(double&, double&);
template void klasa::swap<char>(char&, char&);
template void klasa::swap<short>(short&, short&);
template void klasa::swap<long double>(long double&, long double&);

template <typename T> void klasa::swap(T &a, T &b) {
	T temp = a;
	a = b;
	b = temp;
}

template <> void klasa::swap<long double>(long double& a, long double &b) {
	a++; b++;
}

Mam jakąś tam dziwną potrzebę by dla typu long double funkcja zachowała się inaczej, gdy usunę:

template void klasa::swap<long double>(long double&, long double&);

To wtedy przy wywołaniu funkcji dla danych long double mam błąd kompilacji. Gdy zostawię to też mam błąd...

Jak temu zapobiec?

1
komentarz 9 marca 2018 przez mokrowski Mędrzec (155,640 p.)

Ok... 

Jeśli dobrze zrozumiałem pytania, to w zasadzie odpowiedź jest jedna na obydwa.

Prosty przykład. Mamy klasę o nazwie Klasa zapisaną w 1 pliku klasa.hpp. Będzie trywialna ale "wyciągnę" jej metody poza ciało deklaracji wygląda tak:

#pragma once

template<typename T>
class Klasa {
public:
    Klasa(T dane_);
    void setDane(T wartosc);
    T getDane() const;
private:
    T dane;
};

template<typename T>
Klasa<T>::Klasa(T dane_) : dane{dane_} {}

template<typename T>
void Klasa<T>::setDane(T wartosc) {
    dane = wartosc;
}

template<typename T>
T Klasa<T>::getDane() const {
    return dane;
}

... a sam main.cpp tak:

#include <iostream>
#include "klasa.hpp"

int main() {
    Klasa<int> klasa(12);
    std::cout << klasa.getDane() << '\n';
    klasa.setDane(42);
    std::cout << klasa.getDane() << '\n';
}

Nic specjalnego się nie dzieje. Tak naprawdę jednostką kompilacji jest main.o i w nim jest włączony w ciało pliku (przez #include ) sam nagłówek. main() ma także użycie Klasa<int> więc powstanie konkretyzacja szablonu dla Klasa od typu int.

Teraz jednak przenoszę implementację szablonu metod klasy Klasa, do pliku klasa.cpp i usuwam je z klasa.hpp

Wtedy plik klasa.hpp wygląda tak:

#pragma once

template<typename T>
class Klasa {
public:
    Klasa(T dane_);
    void setDane(T wartosc);
    T getDane() const;
private:
    T dane;
};

.. a plik implementacji klasa.cpp tak:

#include "klasa.hpp"

template<typename T>
Klasa<T>::Klasa(T dane_) : dane{dane_} {}

template<typename T>
void Klasa<T>::setDane(T wartosc) {
    dane = wartosc;
}

template<typename T>
T Klasa<T>::getDane() const {
    return dane;
}

Pliku main nie zmieniam i (dla porządku) wygląda tak:

#include <iostream>
#include "klasa.hpp"

int main() {
    Klasa<int> klasa(12);
    std::cout << klasa.getDane() << '\n';
    klasa.setDane(42);
    std::cout << klasa.getDane() << '\n';
}

Teraz kompiluję jednostki kompilacji. Są 2 jest to main.cpp do main.o i klasa.cpp do klasa.o

clang++ -c main.cpp 
clang++ -c klasa.cpp

Czas na konsolidację... 

clang++ -o main main.o klasa.o 
Undefined symbols for architecture x86_64:
  "Klasa<int>::setDane(int)", referenced from:
      _main in main.o
  "Klasa<int>::Klasa(int)", referenced from:
      _main in main.o
  "Klasa<int>::getDane() const", referenced from:
      _main in main.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Błąd na etapie konsolidacji (linkowania)! Brakuje specjalizacji dla Klasa<int>! To teraz sprawdzę czy Klasa<int> jest obecna w pliku klasa.o, czy kompilator ją wbudował:

objdump -tC klasa.o

klasa.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 klasa.cpp
0000000000000000 l    d  .text	0000000000000000 .text
0000000000000000 l    d  .data	0000000000000000 .data
0000000000000000 l    d  .bss	0000000000000000 .bss
0000000000000000 l    d  .note.GNU-stack	0000000000000000 .note.GNU-stack
0000000000000000 l    d  .comment	0000000000000000 .comment

I nie widać nigdzie konkretyzacji dla Klasa<int>. No to do klasa.cpp dodam prośbę o konkretyzację:

#include "klasa.hpp"

template<typename T>
Klasa<T>::Klasa(T dane_) : dane{dane_} {}

template<typename T>
void Klasa<T>::setDane(T wartosc) {
    dane = wartosc;
}

template<typename T>
T Klasa<T>::getDane() const {
    return dane;
}

// Prośba o konkretyzację Klasa dla int
template class Klasa<int>;

Jak teraz skompiluję:

clang++ -c klasa.cpp

To obiekt (plik klasa.o) raportuje wbudowanie tej konkretyzacji:

objdump -tC klasa.o

klasa.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 klasa.cpp
0000000000000000 l    d  .text	0000000000000000 .text
0000000000000000 l    d  .data	0000000000000000 .data
0000000000000000 l    d  .bss	0000000000000000 .bss
0000000000000000 l    d  .text._ZN5KlasaIiEC2Ei	0000000000000000 .text._ZN5KlasaIiEC2Ei
0000000000000000 l    d  .text._ZN5KlasaIiE7setDaneEi	0000000000000000 .text._ZN5KlasaIiE7setDaneEi
0000000000000000 l    d  .text._ZNK5KlasaIiE7getDaneEv	0000000000000000 .text._ZNK5KlasaIiE7getDaneEv
0000000000000000 l    d  .note.GNU-stack	0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame	0000000000000000 .eh_frame
0000000000000000 l       .group	0000000000000000 Klasa<int>::Klasa(int)
0000000000000000 l    d  .comment	0000000000000000 .comment
0000000000000000 l    d  .group	0000000000000000 .group
0000000000000000 l    d  .group	0000000000000000 .group
0000000000000000 l    d  .group	0000000000000000 .group
0000000000000000  w    F .text._ZN5KlasaIiEC2Ei	0000000000000017 Klasa<int>::Klasa(int)
0000000000000000  w    F .text._ZN5KlasaIiEC2Ei	0000000000000017 Klasa<int>::Klasa(int)
0000000000000000  w    F .text._ZN5KlasaIiE7setDaneEi	0000000000000017 Klasa<int>::setDane(int)
0000000000000000  w    F .text._ZNK5KlasaIiE7getDaneEv	0000000000000010 Klasa<int>::getDane() const

I konsolidacja się powiedzie:

clang++ -o main main.o klasa.o

Mam nadzieję że teraz rozumiesz że konkretyzacja trafia do pliku *.o albo poprzez wczytanie nagłówka albo poprzez prośbę by została zrobiona w *.cpp

Jeśli chcesz uniknąć problemu, to stara (i nie do końca prawidłowa) wykładnia mówi "jak masz szablony, implementację funkcji/klas szablonowych umieść w *.hpp". Wtedy wszystkie niezbędne informacje do konkretyzacji klasy "wciągnie" dana jednostka kompilacji (czyli plik *.cpp) poprzez #include.

Lepiej jednak zachowując kontrolę z jakimi typami udaje się konsolidować całość programu, implementację nawet funkcji/klas szablonowych, przenieść do pliku *.cpp bo nie pokazujesz samej implementacji w *.hpp i dodatkowo masz kontrolę kto i jak tego będzie używał (tj. z jakimi typami).

Dobra koniec... długie wyszło... 

komentarz 9 marca 2018 przez Jakub 0 Pasjonat (23,120 p.)
Dziękuje bardzo za ciężką pracę z napisaniem treściwej odpowiedzi :) Rozumiem już myślę dużo lepiej czemu musimy w takich sytuacjach stosować konkretyzacje. Nie chce naginać cierpliwości ale jakoś nie mogłem zbytnio wyłapać odpowiedzi na drugie pytanie. Jeśli tam jest to będę wdzięczny za jakieś nakierowanie. Mam nadzieje że to ostatnia już moja niepewność.
komentarz 9 marca 2018 przez mokrowski Mędrzec (155,640 p.)

Możesz napisać maksymalnie krótki przykład tego błędu? Nie wiem o jaki dokładnie błąd chodzi.

komentarz 9 marca 2018 przez Jakub 0 Pasjonat (23,120 p.)

Mam taki kod:

klasa.h

class klasa
{
public:
	template <typename T> void swap(T &a, T &b); //prototyp szablonu 
	template <> void swap<long double>(long double& a, long double &b); //prototyp specjalizacji 
        //tego szablonu dla argumentów typu long double 
};

klasa.cpp

#include "klasa.h"

 //jawna konkretyzacja dla jakiś tam typów : 
template void klasa::swap<int>(int&, int&); 
template void klasa::swap<long double>(long double&, long double&);


//definicja szablonu funkcji :
template <typename T> void klasa::swap(T &a, T &b) { 
	T temp = a;
	a = b;
	b = temp;
}

// jawna specjalizacja szablonu funkcji dla typu double :
template <> void klasa::swap<long double>(long double& a, long double &b) { 
	a++; b++; //jakiś przykładowy inny kod 
}

main:

#include <iostream>
#include "klasa.h"

int main(){
	klasa kl;
	char a = 'q', b = 'e';
	std::cout << a << " " << b << std::endl;
	kl.swap(a, b);
	std::cout << a << " " << b << std::endl;

        long double x = 6, y = 8; //chcę by dla tego typu nastąpiła specjalizacja...
	std::cout << x << " " << y << std::endl;
	kl.swap(x, y);
	std::cout << x << " " << y << std::endl;

    return 0;
}

Przy kompilacji tego programu mam błędy:

 liczba nierozpoznanych elementów zewnętrznych: 1  

   nierozpoznany zewnętrzny symbol "public: void __thiscall klasa::swap<long double>(long double &,long double &)" (??$swap@O@klasa@@QAEXAAO0@Z) przywołany w funkcji _main    ConsoleApplication1   

 

Wiem  z czego te błędy wynikają: 

Nie wiem jednak czemu to jest właściwie błędem.

1
komentarz 10 marca 2018 przez mokrowski Mędrzec (155,640 p.)

Odnosząc się do przykładu.

W pliku klasa.h zrobiłeś błąd karząc kompilatorowi użyć specjalizacji w ciele klasy gdy jeszcze nie wie jak ma wyglądać samo ciało tej funkcji. Tego nie powinno tam być.

W pliku klasa.cpp umieściłeś konkretyzacje także jeszcze przed tym kiedy kompilator wie jak je zbudować. Przesuń je pod definicje szablonu funkcji. Specjalizacja szablonu powinna być także umieszczona przed konkretyzacją.

Plik translacji przetwarzany jest "od góry do dołu". Jeśli wcześniej nie ma definicji elementu a na danym etapie jej używasz, będzie błąd.

Zawartość plików (zostawiłem kod ew. go komentując):

klasa.h

#pragma once

class klasa
{
public:
    template <typename T> void swap(T &a, T &b); //prototyp szablonu 
    // Za wcześnie. Jeszcze nie ma definicji ciała funkcji.
    //template <> void swap<long double>(long double& a, long double &b); //prototyp specjalizacji 
        //tego szablonu dla argumentów typu long double 
};

klasa.cpp

#include "klasa.h"

// Aby dalej konkretyzować/specjalizować, należy znać ciało funkcji
//definicja szablonu funkcji :
template <typename T> void klasa::swap(T &a, T &b) { 
    T temp = a;
    a = b;
    b = temp;
}

// Następne będą ew. specjalizacje.
// jawna specjalizacja szablonu funkcji dla typu double :
template <> void klasa::swap<long double>(long double& a, long double &b) { 
    a++; b++; //jakiś przykładowy inny kod 
}

// Na końcu konkretyzacje
//jawna konkretyzacja dla jakiś tam typów : 
template void klasa::swap<int>(int&, int&); 
template void klasa::swap<long double>(long double&, long double&);

// Z powodu umieszczenia definicji szablonów w pliku *.cpp 
// oraz użycia w main() klasa::swap dla char, powinna być tu 
// konkretyzacja
template void klasa::swap<char>(char &, char&);

main.cpp nie był zmieniony.

Zasada jest dość prosta. Specjalizacja informuje kompilator jak tworzyć kod jeśli będzie tego typu prośba, konkretyzacja emituje kod (czyli progam w języku hosta). Do poprawnej emisji kodu niezbędna jest znajomość ew. specjalizacji (jeśli są konieczne). Jeśli będziesz konkretyzował kod przed faktem jego pełnego zdefiniowania i ew. wyspecjalizowania, będzie błąd.

To jest błędny plik:

#include "klasa.h"

// Aby dalej konkretyzować/specjalizować, należy znać ciało funkcji
//definicja szablonu funkcji :
template <typename T> void klasa::swap(T &a, T &b) { 
    T temp = a;
    a = b;
    b = temp;
}


//jawna konkretyzacja dla jakiś tam typów : 
template void klasa::swap<int>(int&, int&); 
template void klasa::swap<long double>(long double&, long double&);

// Z powodu umieszczenia definicji szablonów w pliku *.cpp 
// oraz użycia w main() klasa::swap dla char, powinna być tu 
// konkretyzacja
template void klasa::swap<char>(char &, char&);

// jawna specjalizacja szablonu funkcji dla typu double :
template <> void klasa::swap<long double>(long double& a, long double &b) { 
    a++; b++; //jakiś przykładowy inny kod 
}

.. bo konkretyzacje występują przed specjalizacjami.

Kolejność:

1. Deklaracja 

2. Definicja

3. Specjalizacja

4. Konkretyzacja

komentarz 11 marca 2018 przez Jakub 0 Pasjonat (23,120 p.)
Jeszcze raz dziękuje za wytłumaczenie mi tego wszystkiego, bez forum nie dał bym rady. Teraz muszę tylko to jakoś przetrawić, zrobić notatki i napisać jakieś ćwiczenia z użyciem szablonów.

Podobne pytania

+1 głos
3 odpowiedzi 997 wizyt
0 głosów
1 odpowiedź 1,662 wizyt
pytanie zadane 28 marca 2017 w C i C++ przez Evelek Nałogowiec (28,960 p.)
0 głosów
2 odpowiedzi 372 wizyt
pytanie zadane 18 listopada 2017 w C i C++ przez Bartek Franczak Nowicjusz (160 p.)

92,620 zapytań

141,474 odpowiedzi

319,813 komentarzy

62,003 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...