配对从可变参数模板中随机获得的兼容类型

Pairing compatible types obtained randomly from variadic templates

A Bow 只能触发类型为 ArrowBoltDartMissile(但不能触发 Stone) ,并且只能与 QuiverCase 类型的 MissileContainer 一起使用。 Quiver 只能装箭或螺栓,而 Case 只能装螺栓、飞镖或石头。我已经声明了这一点:

struct Bow : MissileFireWeapon, MissileTypes<Arrow, Dart, Bolt>,
    MissileContainerTypes<Quiver, Case> { /*... */ };

struct Quiver : MissileContainer, MissileTypes<Arrow, Bolt> {};

struct Case : MissileContainer, MissileTypes<Bolt, Dart, Stone> {};

哪里

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;
};

所以通过使用

const int a = std::rand() % T::numMissileContainerTypes;
const int b = std::rand() % T::numMissileTypes;

然后我可以为 T 类型的任何 MissileWeapon 创建一个 MissileContainer(使用已经有效但与问题无关的模板递归内容)。问题在于,虽然由 b 确定的结果导弹将可用于 T 类型的 MissileWeapon,但导弹可能与 a 确定的 MissileContainer 不兼容。我该如何解决这个问题,如果我改变了我对 Bow 可以触发什么以及 QuiverCase 可以保持什么的想法,唯一需要的改变只是在 BowQuiverCase 声明处而不是其他地方?这是说明我在说什么的完整编译示例:

#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();
}

ab 的随机值可能没有正确配对。如何在保持代码可维护性的同时确保它们是正确的?

如果您想在生产级别查看此代码,请看这里(尽管名称不同):

#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())
}

更新:我刚刚想到的一个想法:写一个元函数来获取两个包的交集并使用该交集来确定导弹类型?

好的,我得到了一个与交集想法相关的可行解决方案:

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;
}

不确定它的效率如何,但它可以工作,并且代码保留了我指定的可维护性。由于 IntersectPacks 适用于任意数量的包,我认为如果有两个以上的 classes(即不仅仅是 MissileFireWeaponMissileContainer)限制,此解决方案也适用哪些导弹是合适的。只需交叉与每个 class.

关联的所有导弹类型

虽然我欢迎其他想法,因为这个解决方案看起来很长,并且从我之前的问题中我了解到我的长尝试通常会让人感到羞耻,但这里的专家给出了更短和更优雅的解决方案。