2

I have a class storing a fixed size amount of data

template<size_t SIZE> class Data {...};

Now I have different algorithms for generating data (e.g. pseudorandom generators):

class PseudoRandom1 {
   template<size_t SIZE> Data<SIZE> generate();
};

class PseudoRandom2 {
   template<size_t SIZE> Data<SIZE> generate();
};

Now I want to have a dynamic runtime decision as to which of these generators to call. Using virtual templates (which, I know, is not possible), it would be the following:

class Generator {
  virtual template<size_t SIZE> Data<SIZE> generate() = 0;
};

class PseudoRandomX : public Generator {
  template<size_t SIZE> Data<SIZE> generate() override {...}
};

Unfortunately, I cannot change the SIZE parameter of the data class to be a non-template runtime parameter. Furthermore, I need the actual generator instance to be a runtime decision, because the user can choose a generator algorithm. If possible, I'd prefer a typesafe solution (i.e. no boost::any).

I know virtual templates are not possible. Is there another way to solve this?

4

4 回答 4

2

If you can turn to a template your Generator class, you can use a template method approach like the following one:

#include<cstdlib>

template<size_t SIZE> class Data { };

template<size_t SIZE>
class Generator {
public:
    using Ret = Data<SIZE>;
    static Data<SIZE> generate() { };
};

template<class Gen>
class PseudoRandomX {
    typename Gen::Ret generate() { return Gen::generate(); }
};

int main() {
    auto prx = new PseudoRandomX<Generator<16>>{};
}

Of course, for simplicity I defined as static the generate method, but you can easily switch to the non static version of the solution with a few changes (as an example, it could depends on the fact that your Generator classes are state-less or not and the static method could not match your requirements, I cannot say it from what I see in your question).

EDIT

I've just seen that your are looking for a factory with the generator chosen at runtime.

It follows a slightly different solution, maybe it fits better your requirement:

#include<cstdlib>
#include<memory>

template<size_t SIZE> class Data { };

template<size_t SIZE>
class Generator {
public:
    const static size_t gsize = SIZE;
    using GData = Data<SIZE>;
    static Data<SIZE> generate() { };
};

template<size_t SIZE>
class BasePseudoRandom {
public:
    virtual Data<SIZE> generate() = 0;
};

template <size_t SIZE, class Gen>
class ConcretePseudoRandom: public BasePseudoRandom<Gen::gsize> {
public:
    typename Gen::GData generate() { return Gen::generate(); }
};

class Factory {
public:
    template<class Gen>
    static std::shared_ptr<BasePseudoRandom<Gen::gsize>> create(Gen*) {
        return std::make_shared<ConcretePseudoRandom<Gen::gsize, Gen>>();
    }
};

int main() {
    Generator<16> gen;
std::shared_ptr<BasePseudoRandom<16>> pr = Factory::create(&gen);
    pr->generate();
}

This way, you simply push your generator within the factory and get back a pseudorandom generator built upon that generator, the latter respecting a well defined interface.

于 2015-11-03T23:43:21.923 回答
2

Here is an example using CRTP which dynamically registers a set of generators at runtime (c-startup). A singleton factory pattern is used to dynamically create new instances of data at runtime according to selected algorithms defined in the implementation.

The design is split into two parts (namespace util). The first part consists of the PseudoRandomGenerator Base and Template class and Factory for all generators. The second part is for your implementation of the various data generation algorithms. Each of the implementation classes in the second part can be split apart into separate files (3 in our case).

Working example 1

#include <iostream>
#include <string>
#include <map>
#include <memory>

namespace PseudoRandomGeneratorTypes { enum : int { Type1, Type2, Type3 }; }

namespace util {

    template<size_t SIZE>
    struct __Data {
        int a;
    };

    using Data = __Data<10>;

    class PseudoRandomGenerator {
    protected:
        PseudoRandomGenerator() {}

    public:
        auto getType() const { return _type; }
        virtual Data generate() const = 0;
    protected:
        int _type;
    };

    template<int PRGType, typename PRGImpl>
    class PRGTmpl : public PseudoRandomGenerator {

    public:

        static PseudoRandomGenerator* CreatePtr() {
            return new PRGImpl();
        }

        static const int TYPE;

    protected:
        PRGTmpl() { _type = TYPE; }

    };

    class PseudoRandomGeneratorFactory {
    public:
        typedef PseudoRandomGenerator* (*psg)();

        static auto get()
        {
            static PseudoRandomGeneratorFactory fact;
            return &fact;
        }

        auto Register(int id, psg m)
        {
            _map[id] = m;
            return id;
        }

        auto Create(int id)
        {
            return _map[id]();
        }

    private:
        PseudoRandomGeneratorFactory() {}
        ~PseudoRandomGeneratorFactory() {}

        std::map<int, psg> _map;

    };

    template <int arbitaryPRGType, typename arbitaryPRGImpl>
    const int PRGTmpl<arbitaryPRGType, arbitaryPRGImpl>::TYPE = PseudoRandomGeneratorFactory::get()->Register(arbitaryPRGType, &PRGTmpl<arbitaryPRGType, arbitaryPRGImpl>::CreatePtr);

}

namespace util {

    class PRGType1 : public PRGTmpl < PseudoRandomGeneratorTypes::Type1, PRGType1 > {
    public:
        virtual Data generate() const override final { return Data{ 111 }; }
    };
    template class PRGTmpl < PseudoRandomGeneratorTypes::Type1, PRGType1 >;

    class PRGType2: public PRGTmpl < PseudoRandomGeneratorTypes::Type2, PRGType2 > {
    public:
        virtual Data generate() const override final { return Data{ 222 }; }
    };
    template class PRGTmpl < PseudoRandomGeneratorTypes::Type2, PRGType2 >;

    class PRGType3 : public PRGTmpl < PseudoRandomGeneratorTypes::Type3, PRGType3 > {
    public:
        virtual Data generate() const override final { return Data{ 333 }; }
    };
    template class PRGTmpl < PseudoRandomGeneratorTypes::Type3, PRGType3 >;

}

using namespace util;
using namespace std;

int main()
{

    auto rng1 = unique_ptr<PseudoRandomGenerator>(PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type1));
    auto rng2 = unique_ptr<PseudoRandomGenerator>(PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type2));
    auto rng3 = unique_ptr<PseudoRandomGenerator>(PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type3));

    cout << rng1->generate().a << endl;
    cout << rng2->generate().a << endl;
    cout << rng3->generate().a << endl;
}

Furthermore, if you only need a single instance at a time and wish to not use the heap the CreatePtr() func. can be substituted with the following.

static PseudoRandomGenerator* GetPtr() {
    static PRGImpl _m;
    _m = PRGImpl();
    return &_m;
}

The specialisation changes to:

template <int arbitaryPRGType, typename arbitaryPRGImpl>
const int PRGTmpl<arbitaryPRGType, arbitaryPRGImpl>::TYPE = PseudoRandomGeneratorFactory::get()->Register(arbitaryPRGType, &PRGTmpl<arbitaryPRGType, arbitaryPRGImpl>::GetPtr);

and usage pattern changes to:

auto rng1 = PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type1);
auto rng2 = PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type2);
auto rng3 = PseudoRandomGeneratorFactory::get()->Create(PseudoRandomGeneratorTypes::Type3);

In the second example we can use a plain C style array instead of a map to avoid dynamic memory allocation.

Working example 2

Adapted from Dynamically register constructor methods in an AbstractFactory at compile time using C++ templates

于 2015-11-04T01:40:56.753 回答
2

Type erasure is your friend. In your case, you can use std::function, that provides half of the boilerplate. The other half is to write a wrapper that converts your generate into an operator().

template<std::size_t N, class T>
struct generator_adapter
{
    auto operator()() { return generator_.template generate<N>(); }
    T generator_;
};

template<size_t Size>
using AnyRandomGenerator = std::function< Data<Size>() >;

template<size_t Size, class T>
auto as_any_generator(T g)
{
     return AnyRandomGenerator<Size>( generator_adapter<Size,T>{g} );
}

AnyRandomGenerator<4> f = as_any_generator<4>(PseudoRandom1());

Your generate functions will need to be public, or you can make your generators a friend of generator_adapter.

Modify this example to use perfect-forwarding if necessary.

于 2015-11-04T02:03:34.033 回答
1

The following variation will work for at least some purposes consistent with the information you've given so far.

class Generator {
public:
    template< size_t SIZE >
    Data<SIZE> generate() {
        Data<SIZE> x;
        vgenerate(SIZE, x.pointer_to_internals());
        return x;
    }
protected:
    virtual void vgenerate(size_t size, InternalDataPointer *p) = 0;
};

class PseudoRandomX : public Generator {
    void vgenerate(size_t size, InternalDataPointer *p) override {...}
};

Another solution that will work for a different set of purposes is

template< size_t SIZE >
class Generator {
public:
    virtual Data<SIZE> generate() = 0;
};

template< size_t SIZE >
class PseudoRandomX : public Generator<SIZE> {
    Data<SIZE> generate() override {...}
};
于 2015-11-04T02:21:53.247 回答