4

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?

prestokeys
  • 4,817
  • 3
  • 20
  • 43

1 Answers1

0

Ok, I got one working solution with the intersection idea:

template <typename, typename> struct ExistsInPack;

template <typename T, template <typename...> class P>
struct ExistsInPack<T, P<>> : std::false_type {};

template <typename T, template <typename...> class P, typename First, typename... Rest>
struct ExistsInPack<T, P<First, Rest...>> {
    static const bool value = std::is_same<T, First>::value ? true : ExistsInPack<T, P<Rest...>>::value;
};

template <typename, typename, typename> struct IntersectPacksHelper;

template <template <typename...> class P, typename... Types, typename... Accumulated>
struct IntersectPacksHelper<P<>, P<Types...>, P<Accumulated...>> {
    using type = P<Accumulated...>;
};

template <template <typename...> class P, typename First, typename... Rest, typename... Types, typename... Accumulated>
struct IntersectPacksHelper<P<First, Rest...>, P<Types...>, P<Accumulated...>> {
    using type = typename std::conditional<ExistsInPack<First, P<Types...>>::value,
        typename IntersectPacksHelper<P<Rest...>, P<Types...>, P<Accumulated..., First>>::type,
        typename IntersectPacksHelper<P<Rest...>, P<Types...>, P<Accumulated...>>::type
    >::type;
};

template <typename, typename> struct IntersectTwoPacks;

template <template <typename...> class P, typename... Types1, typename... Types2>
struct IntersectTwoPacks<P<Types1...>, P<Types2...>> : IntersectPacksHelper<P<Types1...>, P<Types2...>, P<>> {};

template <typename...> struct IntersectPacks;

template <typename T> struct Identity { using type = T; };

template <>
struct IntersectPacks<> { using type = void; };

template <template <typename...> class P, typename... Types>
struct IntersectPacks<P<Types...>> : Identity<P<Types...>> {};

template <template <typename...> class P, typename... Types1, typename... Types2, typename... Packs>
struct IntersectPacks<P<Types1...>, P<Types2...>, Packs...> :
    IntersectPacks<typename IntersectTwoPacks<P<Types1...>, P<Types2...>>::type, Packs...> {};

template <int N, typename T>
struct MissileFireWeapon::FillMissileContainer : FillMissileContainer<N - 1, T> {
    void operator()(MissileContainer* missileContainer, int n) const {
        if (n == N) {
            using MissilesForThisMissileFireWeapon = typename T::missileTypes;
            using MissileContainerTypes = typename T::missileContainerTypes;
            using ThisMissileContainer = typename MissileContainerTypes::template missileContainerType<N>;
            using MissilesForThisMissileContainer = typename ThisMissileContainer::missileTypes;
            using PossibleMissiles =
typename IntersectPacks<MissilesForThisMissileFireWeapon, MissilesForThisMissileContainer>::type;
  // Now get a random missile type from this pack instead.  For completion, the rest is:
            const int r = std::rand() % PossibleMissiles::numMissileTypes,
                numMissiles = std::rand() % missileContainer->capacity();
            addMissiles<PossibleMissiles>(missileContainer, r, numMissiles);
            return;
        }
        FillMissileContainer<N - 1, T>::operator()(missileContainer, n);
    }
};

template <typename T>
struct MissileFireWeapon::FillMissileContainer<-1, T> {
    void operator()(MissileContainer*, int) const {}  // End of recursion.
};

template <typename T>
    void MissileContainer::fillMissileContainer (MissileContainer* missileContainer, int n) {
    FillMissileContainer<T::numMissileContainerTypes - 1, T>()(missileContainer, n);  // Template recursion.
}

template <typename T>
inline MissileContainer* MissileFireWeapon::createMissileContainerBase() {
    const int n = std::rand() % T::numMissileContainerTypes;
    MissileContainer* missileContainer = getMissileContainer<T>(n);  // Template recursion.
    fillMissileContainer<T> (missileContainer, n);  //*** The key line.
    return missileContainer;
}

Not sure how efficient it is, but it works, and the code retains the maintainability that I specified. And since IntersectPacks works for any number of packs, I think this solution also works if there are more than two classes (i.e. more than just MissileFireWeapon and MissileContainer) restricting which missiles are appropriate. Just intersect all the missiles types associated with each class.

I welcome other ideas though, because this solution seems very longish, and from my previous questions I learned that my longish attempts usually are put to shame but much shorter and more elegant solutions given by the experts here.

prestokeys
  • 4,817
  • 3
  • 20
  • 43