Wyrzucenie implementacji szablonów poza definicje klasy:
wrapper.hpp
#pragma once
#include <string>
#include <iostream>
class Wrapper{
public:
template<class Type>
static Type* get(const std::string&);
template<class Type>
Type member() const;
};
template<class Type>
Type* Wrapper::get(const std::string&) {
std::cout << "Generyczna, typ ogólny!\n";
return nullptr;
}
template<class Type>
Type Wrapper::member() const {
return Type{};
}
template<>
int* Wrapper::get(const std::string&) {
std::cout << "Specjalizowana dla inta!\n";
return new int(1);
}
template<>
int Wrapper::member() const {
return 42;
}
main.cpp
#include "wrapper.hpp"
int main() {
Wrapper w1;
auto p1 = Wrapper::get<double>("abrakadabra");
auto p2 = Wrapper::get<int>("hokus pokus");
std::cout << "get<int>() = " << *p2
<< "\nget<double>() = " << (p1 == nullptr ? "pusty" : "nie")
<< "\nmember<double>() = " << w1.member<double>()
<< "\nmember<int>() = " << w1.member<int>()
<< std::endl;
}
Dodałem jeszcze jakiegoś membera w klasie dla wyczerpania przykładu.
1. Rozwiązanie pierwsze, stosuje np. Qt. Tam jest stosowane z powodu pracy moc'a (Meta Object Compiler). On parsuje pliki *.cpp i rozwija "magiczne makra" :-)
a) Wyrzucamy definicje do pliku wrapper.cpp
#include "wrapper.hpp"
template<class Type>
Type* Wrapper::get(const std::string&) {
std::cout << "Generyczna, typ ogólny!\n";
return nullptr;
}
template<class Type>
Type Wrapper::member() const {
return Type{};
}
template<>
int* Wrapper::get(const std::string&) {
std::cout << "Specjalizowana dla inta!\n";
return new int(1);
}
template<>
int Wrapper::member() const {
return 42;
}
b) Zmieniamy plik wrapper.hpp na:
#pragma once
#include <string>
#include <iostream>
class Wrapper{
public:
template<class Type>
static Type* get(const std::string&);
template<class Type>
Type member() const;
};
#include "wrapper.cpp" // Tak.. dobrze widzisz.. włączanie *.cpp :-)
Wygląda jak wygląda. Mnie się nie podoba... można lepiej :-)
2. Rozwiązanie z dodaniem instancjonowania szablonu.
Pełna zawartość plików:
main.cpp (nie zmienił się)
wrapper.hpp
#pragma once
#include <string>
#include <iostream>
class Wrapper{
public:
template<class Type>
static Type* get(const std::string&);
template<class Type>
Type member() const;
};
Teraz wrapper.hpp wygląda nieźle :-)
wrapper.cpp:
#include "wrapper.hpp"
template<class Type>
Type* Wrapper::get(const std::string&) {
std::cout << "Generyczna, typ ogólny!\n";
return nullptr;
}
template<class Type>
Type Wrapper::member() const {
return Type{};
}
template<>
int* Wrapper::get(const std::string&) {
std::cout << "Specjalizowana dla inta!\n";
return new int(1);
}
template<>
int Wrapper::member() const {
return 42;
}
// Tu jest wymuszenie instancjonowania szablonów..
template double* Wrapper::get<double>(const std::string&);
template double Wrapper::member<double>() const;
W ramach usprawnień, wyrzucam jeszcze te wymuszenia do pliku wrapper_impl.cpp i robię we wrapper.cpp na końcu pliku
#include "wrapper_impl.cpp"
Mam nadzieję że jasne :-)