1

I would like to create a collection of heterogeneous objects; ie. objects of different types.

This would be useful when these objects have similar functionality (including members), but don't derive from the same parent class.

A perfect example of this are random number engines: minstd_rand, mt19937 and ranlux24 are all engines. They have the same members (such as the call operator ()), but don't derive from a common "Engine" class and so are of different types.

The same is the case with random number distributions.

Had there been a common root class 'Engine', I could easily have created a vector of these objects as follows:

vector<Engine> engines {minstd_rand, mt19937, ranlux24};

Having done this, I could then invoke a function in a loop, as follows:

/// Generate 10 random numbers.
void gen(vector<Engine>& engines)
{
    for (auto& e : engines)
       for (int i = 0; i < 10; i++)
           cout << e() << endl;
}


int main()
{
    gen(engines);       /// Invocation
}

However, I can't do this.

If I use a tuple to wrap each engine, each object would have a different type:

tuple<type1>, tuple<type2>, .... 

Again, the types would be heterogeneous and I couldn't create a collection of them.

So the question is, is it possible to create a collection of heterogeneous objects and if so, how?

SSteven
  • 733
  • 5
  • 17
  • Does your engine/wrapper template even compile? – Chenna V Feb 24 '18 at 06:37
  • There were some problems in the original code posted. The wrapped engines would still be heterogeneous types and so couldn't be stored in a vector. I have edited the post. – SSteven Feb 24 '18 at 06:46
  • If you are going to wrap them then why not wrap then in a traditional inheritance hierarchy with a common interface? – Galik Feb 24 '18 at 07:44
  • @Galik, How can this be done? The engines don't derive from a common base class. Can you post your code, please? – SSteven Feb 24 '18 at 16:02

3 Answers3

2

You can use vector<function<size_t ()>> to hold these engines.

using Engine = function<size_t ()>;
vector<Engine> engines = {minstd_rand{}, mt19937{}, ranlux24{}};
for (auto &e : engines) {
    cout << e() << endl;
}
for_stack
  • 21,012
  • 4
  • 35
  • 48
  • I've tried this solution and it works correctly. Though the underlying classes minstd_rand, mt19937 and ranlux24 are different classes, as functions, they have the same type. So the solution works! However, the original query remains : [If we aren't dealing with function objects of the same type], is it possible to store heterogeneous objects in a collection? – SSteven Feb 24 '18 at 06:58
  • The [following post](https://stackoverflow.com/questions/7804955/heterogeneous-containers-in-c) gives some useful answers for storing heterogeneous objects in a collection. It is marked as the "correct answer". – SSteven Feb 24 '18 at 07:08
1

You can simply create your own polymorphic hierarchy to wrap your different separately typed pseudo random number generators in. This is made easier by the fact that the different standard generators have a common interface even though they do not derive from a common base type.

Something a bit like this:

// Base interface class
class prng
{
public:
    using dist_type = std::uniform_int_distribution<int>;

    virtual ~prng() = default;

    virtual int operator()(int min, int max) = 0;

protected:
    dist_type dist;

    template<typename PRNG>
    static PRNG& eng()
    {
        thread_local static PRNG eng{std::random_device{}()};
        return eng;
    }
};

// smart pointers because polymorphism
using prng_uptr = std::unique_ptr<prng>;

// Generic class takes advantage of the different PRNG's
// similar interfaces
template<typename PRNG>
class typed_prng
: public prng
{
public:
    int operator()(int min, int max) override
        { return dist(eng<PRNG>(), dist_type::param_type(min, max)); }
};

// Some nice names
using prng_minstd_rand = typed_prng<std::minstd_rand>;
using prng_mt19937 = typed_prng<std::mt19937>;
using prng_ranlux24 = typed_prng<std::ranlux24>;

int main()
{
    // A vector of smart base pointers to typed instances
    std::vector<prng_uptr> prngs;

    // Add whatever generators you want
    prngs.push_back(std::make_unique<prng_minstd_rand>());
    prngs.push_back(std::make_unique<prng_mt19937>());
    prngs.push_back(std::make_unique<prng_ranlux24>());

    // numbers between 10 and 50    
    for(auto const& prng: prngs)
        std::cout << (*prng)(10, 50) << '\n';
}
Galik
  • 47,303
  • 4
  • 80
  • 117
-1

[ver 1] @Galik's post effectively demonstrated the basic technique of creating a polymorphic hierarchy, which I explain below.

The post was intended as a demonstration (rather than an implementation) of the technique involved. As such, it doesn't compile: http://coliru.stacked-crooked.com/a/0465c2a11d3a0558

I have corrected the underlying issues and the following version works [ver 2]:
http://coliru.stacked-crooked.com/a/9bb0f47251e6dfed

The technique involved is important. I have voted for @Galik's post.

However, there was 1 issue with @Galik's solution: The random_device() engine was hard-coded in the base class itself. As such, it was always used, regardless of which engine was passed as argument in the sub-class. In fact, the engine passed as argument to the sub-class should have been used as the source of random numbers.

I have corrected this and also changed some of the names in the following version [ver 3]: http://coliru.stacked-crooked.com/a/350eadb55a4bafe7

#include <vector>
#include <memory>               /// unique_ptr
#include <random>
#include <iostream>

/// Declarations ...
class RndBase;          /// Random number base class

/// Generic Random number sub-class
/// takes advantage of the different Engines' similar interfaces
template<typename Eng>
class RndSub;

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args);


/// Implementation ...

/// Random number base class
class RndBase
{
public:
    using dist_type = std::uniform_int_distribution<int>;

    virtual ~RndBase() = default;

    virtual int operator() (int min, int max) = 0;

protected:
    dist_type dist;

    /// factory method
    template<typename Eng>
    static Eng& eng()
    {
        static Eng eng {Eng {}};

        return eng;
    }
};   // RndBase


/// Generic Random number sub-class
/// takes advantage of the different Engines' similar interfaces
template<typename Eng>
class RndSub : public RndBase
{
public:
    /// Generate a random number.
    int operator() (int min, int max) override
    {
        return dist(eng<Eng>(), dist_type::param_type(min, max));
    }
};


int main()
{
    using Eminstd_rand = RndSub<std::minstd_rand>;
    using Emt19937 = RndSub<std::mt19937>;
    using Eranlux24 = RndSub<std::ranlux24>;

    /// smart pointers because of polymorphism
    using pRndBase = std::unique_ptr<RndBase>;

    /// A vector of smart base pointers to typed sub-classes
    std::vector<pRndBase> prndbases;

    /// Add whatever generators you want
    prndbases.push_back(make_unique<Eminstd_rand> ());
    prndbases.push_back(make_unique<Emt19937> ());
    prndbases.push_back(make_unique<Eranlux24> ());

    /// random numbers between 10 and 50
    for(auto const& prb : prndbases)
        std::cout << (*prb) (10, 50) << std::endl;
}


template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
   return std::unique_ptr<T> {new T {args...}};
}   // make_unique()

The explanation of the "Polymorphic Hierarchy" pattern is as follows:
1) We know all the engines have different types, though the same interface.

2) We create an abstract base class (RndBase), which includes this interface. It also defines a parameterized (static) factory method named eng() that creates an object of the parameter Eng and returns a reference to it. It is expected that an engine would be used as the parameter.

3) We create a parameterized sub-class named RndSub that derives from the base class RndBase. This class defines a call operator which returns the random number obtained by invoking the distribution.

4) Effectively, what we have done is as follows:
a) The heterogeneous engines are abstracted by the parameterized sub-class RndSub. Each sub-class is different.
b) However, they now have a single common base-class RndBase.
c) Since there is only a single base class (RndBase), we can now create a vector<RndBase>, which is homogeneous. The sub-classes of RndBase are heterogeneous.
d) Since the interface is common, we can use the interface defined in the base class to invoke the implementation in the sub-class. This implementation invokes the factory method eng() in the base class to obtain an engine, which is passed as argument to the distribution. This returns the random number.

This solution is specifically for Random numbers. I am trying to make a solution for any classes (that have a similar interface).

SSteven
  • 733
  • 5
  • 17
  • In my base class, the `std::random_device` that I pass to the engin's constructor is only there to provide the ***seed*** that initializes whatever random engine you use as your template parameter. Nothing more. It is typical to use `std::random_device` because it is connected to your computers non-deterministic random number supply. My example could be improved, however bypassing the seeding engine (or just a seed value) as (say) a constructor parameter. – Galik Feb 26 '18 at 17:34
  • What you are doing here is passing an unseeded, default constructed random generator as the seeding engine to your engine. I suspect that doing that will make your random generator produce exactly the same sequence of numbers each time you run the program. – Galik Feb 26 '18 at 17:40