Første gang jeg forsøgte at lave template specializations af en metode, fik jeg fejlen explicit specialization in non-namespace scope....
Jeg troede derfor at det ikke var muligt (i hvert fald indenfor C++17-standarden). Heldigvis er begrænsningen ikke så stor som den kan se ud.
Lad os tage udgangspunkt i en struct
fra en tidligere artikel:
enum class character_class { rogue, warrior, sorcerer, }; enum class property { damage, health, character_class, level, is_npc, }; struct character { private: double damage; double health; character_class type; unsigned int level; bool is_npc; public: // ... getters/setters tilføjes her ... };
Der er altså tale om en data-struktur med flere forskellige typer data. For at tilgå data kan vi definere et interface således:
template <property p, typename T> constexpr void set(T value); template <property p, typename T> constexpr T get() const;
Her vil det være oplagt at bruge template-specialiseringer, så lad os prøve at lave en specialisering til set
-metode for property::damage
:
struct character { private: double damage; double health; character_class type; unsigned int level; bool is_npc; public: template <property p, typename T> constexpr void set(T value); template <property p, typename T> constexpr T get() const; // Fejl! template <> constexpr void set<property::damage,double>(double value) { damage = value; } };
Men her får vi den omtalte fejl: explicit specialization in non-namespace scope 'struct character'. Betyder det så, at vi skal opgive denne fremgangsmåde? Nej!
I stedet skal koden bare omskrives, så selve specialiseringen ligger udenfor klassen - som fejlen jo egentlig også indikerer:
struct character { private: double damage; double health; character_class type; unsigned int level; bool is_npc; public: template <property p, typename T> constexpr void set(T value); template <property p, typename T> constexpr T get() const; }; template <> constexpr void character::set<property::damage,double>(double value) { damage = value; }
Nu er selve metode-prototypen med template parametre defineret indenfor klassen, mens specialiseringen står udenfor class scope. Ligeledes kan de andre specialiseringer laves:
template <> constexpr void character::set<property::damage,double>(double value) { damage = value; } template <> constexpr void character::set<property::health,double>(double value) { health = value; } template <> constexpr void character::set<property::character_class,character_class>(character_class value) { type = value; } template <> constexpr void character::set<property::level,unsigned int>(unsigned int value) { level = value; } template <> constexpr void character::set<property::level,int>(int value) { level = value; } // Overload for signed int template <> constexpr void character::set<property::is_npc,bool>(bool value) { is_npc = value; } template <> constexpr double character::get<property::damage,double>() const { return damage; } template <> constexpr double character::get<property::health,double>() const { return health; } template <> constexpr character_class character::get<property::character_class,character_class>() const { return type; } template <> constexpr unsigned int character::get<property::level,unsigned int>() const { return level; } template <> constexpr bool character::get<property::is_npc,bool>() const { return is_npc; }
Dette design vil ikke altid være optimalt - f.eks. hvis der er mange felter af samme type, er der meget smartere alternativer som vi ser på omlidt. Til gengæld viser det, hvordan template-specialiseringer kan laves til metoder, hvilket kan være nyttigt.
Det er altså ikke bare muligt, men også ret nemt at lave template-specialiseringer af metoder - de skal bare flyttes ud af class scope.
Til slut vil jeg vise et eksempel på brug af template-specialiseringer, som skalerer bedre hvis du har mange felter af samme type:
enum class property { damage, health, mana, level, gold, experience, }; struct character { private: std::map<property,double> _doubles; std::map<property,int> _integers; template <typename T> inline std::map<property,T>& get_map(); template <typename T> inline const std::map<property,T>& get_map() const; public: template <property p, typename T> constexpr void set(T value) { get_map<T>()[p] = value; } template <property p, typename T> constexpr T get() const { const auto& map = get_map<T>(); auto i = map.find(p); return (i != map.end()) ? i->second : std::numeric_limits<T>::quiet_NaN(); } }; template <> inline auto character::get_map<double>() -> decltype(_doubles)& { return _doubles; } template <> inline auto character::get_map<double>() const -> const decltype(_doubles)& { return _doubles; } template <> inline auto character::get_map<int>() -> decltype(_integers)& { return _integers; } template <> inline auto character::get_map<int>() const -> const decltype(_integers)& { return _integers; }
Med dette design kan nye property
-elementer tilføjes ved kun at udvide denne enum
!
Og det bruges ligeså nemt som tidligere:
character player; player.set<property::damage>(10.0); player.set<property::health>(100.0); player.set<property::mana>(50.0); player.set<property::level>(4); player.set<property::gold>(500); player.set<property::experience>(120000);
Til gengæld er der ikke noget type-check knyttet til property
.
Dette kan klares ved at bruge en assert_type
-funktion som beskrevet her.