A Bow
can only fire a Missile
of type Arrow
, Bolt
, or Dart
(but not a Stone
), and can only go with a MissileContainer
of type Quiver
or Case
. A Quiver can only hold Arrows or Bolts, and a Case can only hold Bolts, Darts, or Stones. I've declared this with:
struct Bow : MissileFireWeapon, MissileTypes<Arrow, Dart, Bolt>,
MissileContainerTypes<Quiver, Case> { /*... */ };
struct Quiver : MissileContainer, MissileTypes<Arrow, Bolt> {};
struct Case : MissileContainer, MissileTypes<Bolt, Dart, Stone> {};
where
template <typename First, typename... Rest>
struct MissileTypes<First, Rest...> {
static const int numMissileTypes = sizeof...(Rest) + 1;
template <int N> using missileType = typename NthType<N, First, Rest...>::type;
};
template <typename First, typename... Rest>
struct MissileContainerTypes<First, Rest...> {
static const int numMissileContainerTypes = sizeof...(Rest) + 1;
template <int N> using missileContainerType = typename NthType<N, First, Rest...>::type;
};
So by using
const int a = std::rand() % T::numMissileContainerTypes;
const int b = std::rand() % T::numMissileTypes;
I can then create a MissileContainer for any MissileWeapon of type T
(using template recursion stuff that already works but is not relevant to the problem). The problem is that though the resulting missiles determined by b
will be usable by the MissileWeapon of type T
, the missiles may not be compatible with the MissileContainer determined by a
. How do I fix this problem in such a way that if I ever change my mind about what a Bow
can fire and what a Quiver
or Case
can hold, the only changes needed would only be at the Bow
, Quiver
and Case
declarations and nowhere else? Here is the full compiling example illustrating what I'm talking about:
#include <iostream>
#include <list>
#include <cstdlib>
#include <ctime>
struct Item {};
struct Weapon : Item {};
struct Missile : Weapon {};
struct Arrow : Missile {};
struct Dart : Missile {};
struct Bolt : Missile {};
struct Stone : Missile {};
struct MissileFireWeapon : Weapon {
virtual struct MissileContainer* createMissileContainer() const = 0;
protected:
template <typename T> static inline MissileContainer* helper();
};
template <typename...> struct MissileTypes;
template <typename First, typename... Rest>
struct MissileTypes<First, Rest...> {
static const int numMissileTypes = sizeof...(Rest) + 1;
// template <int N> using missileType = typename NthType<N, First, Rest...>::type;
};
template <typename...> struct MissileContainerTypes;
template <typename First, typename... Rest>
struct MissileContainerTypes<First, Rest...> {
static const int numMissileContainerTypes = sizeof...(Rest) + 1;
// template <int N> using missileContainerType = typename NthType<N, First, Rest...>::type;
};
struct MissileContainer : Item {};
struct Quiver : MissileContainer, MissileTypes<Arrow, Bolt> {}; // Quiver only holds Arrows and Bolts.
struct Case : MissileContainer, MissileTypes<Bolt, Dart, Stone> {}; // Case only holds Bolts and Darts.
struct Bow : MissileFireWeapon, MissileTypes<Arrow, Dart, Bolt>, MissileContainerTypes<Quiver, Case> {
virtual MissileContainer* createMissileContainer() const override {return helper<Bow>();}
};
template <typename T>
inline MissileContainer* MissileFireWeapon::helper() {
const int a = std::rand() % T::numMissileContainerTypes;
const int b = std::rand() % T::numMissileTypes;
std::cout << "Missile container type = " << a << std::endl; // displaying only
std::cout << "Missile type = " << b << std::endl; // displaying only
MissileContainer* m;
// Construct m from and a and b (using template recursion).
return m;
}
int main() {
const int srand_seed = static_cast<int>(std::time(nullptr));
std::srand (srand_seed);
MissileFireWeapon* missileFireWeapon = new Bow; // Using Bow as an example.
MissileContainer* missileContainer = missileFireWeapon->createMissileContainer();
}
The random values of a
and b
may not be appropriately paired. How to ensure that they are while keeping maintainability of the code?
If you want to see this code at the production level, here that is (though the names are different):
#include <iostream>
#include <list>
#include <string>
#include <cstdlib>
#include <ctime>
#include <typeinfo>
#define show(variable) std::cout << #variable << " = " << variable << std::endl;
struct Item {};
struct Weapon : Item {};
struct Missile : Weapon { virtual std::string tag() const = 0; };
struct Arrow : Missile { virtual std::string tag() const override {return "Arrow";} };
struct Dart : Missile { virtual std::string tag() const override {return "Dart";} };
struct Piercer : Missile { virtual std::string tag() const override {return "Piercer";} };
struct Bolt : Missile { virtual std::string tag() const override {return "Bolt";} };
struct Quarrel : Missile { virtual std::string tag() const override {return "Quarrel";} };
struct MissileFireWeapon : Weapon {
virtual struct MissileContainer* createMissileContainer() const = 0;
protected:
template <typename T> static inline MissileContainer* createMissileContainerBase();
private:
template <int, typename> struct AddMissiles;
template <int, typename> struct GetMissileContainer;
template <typename T> static void addMissiles (MissileContainer* m, int n, int numMissiles) {
AddMissiles<T::numMissileTypes - 1, T>()(m, n, numMissiles);
}
template <typename T> static MissileContainer* getMissileContainer (int n) {return GetMissileContainer<T::numMissileContainerTypes - 1, T>()(n);}
};
struct MissileContainer : Item {
std::list<Missile*> missiles;
template <typename T> void addSpecificMissile (int amount) {
for (int i = 0; i < amount; i++)
missiles.emplace_back (new T);
}
virtual int capacity() const = 0;
virtual std::string tag() const = 0;
};
template <int N, typename T>
struct MissileFireWeapon::GetMissileContainer { // Template recursion to determine which MissileContainer will go with this MissileFireWeapon.
MissileContainer* operator() (int n) {
if (n == N)
return new typename T::template missileContainerType<N>; // Template disambiguator
return GetMissileContainer<N - 1, T>()(n);
}
};
template <typename T>
struct MissileFireWeapon::GetMissileContainer<-1, T> {
MissileContainer* operator() (int) {return nullptr;} // End of recursion
};
template <int N, typename T>
struct MissileFireWeapon::AddMissiles { // Template recursion to determine which Missile will go with this MissileFireWeapon.
void operator() (MissileContainer* m, int n, int numMissiles) {
if (n == N) {
m->template addSpecificMissile<typename T::template missileType<N>>(numMissiles); // Template disambiguator needed in two places!
return;
}
AddMissiles<N - 1, T>()(m, n, numMissiles);
}
};
template <typename T>
struct MissileFireWeapon::AddMissiles<-1, T> {
void operator() (MissileContainer*, int, int) {} // End of recursion
};
template <int, typename ...> struct NthType; // Gets the Nth type from a template pack of types.
template <typename First, typename... Rest>
struct NthType<0, First, Rest...> {
using type = First;
};
template <int N, typename First, typename... Rest>
struct NthType<N, First, Rest...> : NthType<N - 1, Rest...> {};
template <typename...> struct MissileTypes;
template <typename First, typename... Rest>
struct MissileTypes<First, Rest...> {
using primaryMissile = First;
static const int numMissileTypes = sizeof...(Rest) + 1;
template <int N> using missileType = typename NthType<N, First, Rest...>::type;
};
template <typename...> struct MissileContainerTypes;
template <typename First, typename... Rest>
struct MissileContainerTypes<First, Rest...> {
using primaryMissileContainer = First;
static const int numMissileContainerTypes = sizeof...(Rest) + 1;
template <int N> using missileContainerType = typename NthType<N, First, Rest...>::type;
};
struct Quiver : MissileContainer, MissileTypes<Arrow, Dart> { // Quiver only holds Arrows and Darts.
virtual int capacity() const override {return 20;}
virtual std::string tag() const override {return "Quiver";}
};
struct Case : MissileContainer, MissileTypes<Bolt, Quarrel> { // Case only holds Bolts and Quarrels.
virtual int capacity() const override {return 20;}
virtual std::string tag() const override {return "Case";}
};
struct Pouch : MissileContainer {
virtual int capacity() const override {return 20;} // 20 sling stones, but can hold 50 blowgun needles.
virtual std::string tag() const override {return "Pouch";}
};
struct LongBow : MissileFireWeapon, MissileTypes<Arrow, Dart, Piercer>, MissileContainerTypes<Quiver, Case> {
virtual MissileContainer* createMissileContainer() const override {return createMissileContainerBase<LongBow>();}
};
struct CrossBow : MissileFireWeapon, MissileTypes<Bolt, Quarrel>, MissileContainerTypes<Case> {
virtual MissileContainer* createMissileContainer() const override {return createMissileContainerBase<CrossBow>();}
};
template <typename T>
inline MissileContainer* MissileFireWeapon::createMissileContainerBase() {
const int m = std::rand() % T::numMissileContainerTypes;
MissileContainer* missileContainer = getMissileContainer<T>(m); // Template recursion.
const int r = std::rand() % T::numMissileTypes,
numMissiles = std::rand() % missileContainer->capacity() / 2 + missileContainer->capacity() / 2 + 1;
addMissiles<T>(missileContainer, r, numMissiles);
std::cout << "Missile container type = " << m << std::endl; /////
std::cout << "Missile type = " << r << std::endl; /////
return missileContainer;
}
int main() {
const int srand_seed = static_cast<int>(std::time(nullptr));
std::srand (srand_seed);
MissileFireWeapon* missileFireWeapon = new LongBow;
// A randomly created MissileContainer carrying missiles for the LongBow.
MissileContainer* missileContainer = missileFireWeapon->createMissileContainer();
show(missileContainer->tag()) // Displaying the missile container type.
show(missileContainer->missiles.size()) // Displaying the number of missiles.
show(missileContainer->missiles.front()->tag()) // Displaying the missile type.
missileFireWeapon = new CrossBow;
missileContainer = missileFireWeapon->createMissileContainer();
show(missileContainer->tag())
show(missileContainer->missiles.size())
show(missileContainer->missiles.front()->tag())
}
Update: An idea I just thought of: Write a meta-function to obtain the intersection of two packs and use that intersection to determine the missile type?