tags:

views:

135

answers:

3

Hello, I've some code that produces a set of tr1::array of different sizes, but same type, like

array<int, 2>
array<int, 4>
array<int, 6>

The number of these arrays, and their sizes, are given in compile time, so I know exactly how many of them there will be and how's big each one (but they may be different).

Problem: I would like to put them in a collection (using array<> would be great), but type must be equal for all the members and this is not the case.

I thought about using boost::variant, but how can specify a variant with a compile-time determined list of types (I'm thinking about an heavy usage of the preprocessor...)? What about using boost::any? Other methods? (Wild pointers?)

TIA ~Aki

Correction: preprocessor is not usable in this case.

A: 

The only way you can put classes of different types in a STL container is if the container contains pointers (references don't work because they are not default constructable) to some base type and the objects you want to collect all inherit from that type. Note that a container (or any template class) of a type which inherits from a base class does not inherit from a container of the base type. You could just use void* but you need to do a lot of ugly and dangerous casting and you have to remember to free the memory yourself. Why don't you write a fixed size array class that allow you to set the size in the constructor? if you write as wrapper around araray it should not be too much work. If you want to use some solution based on smart pointers don't be tempted to use auto_ptr as the copy semantics are wrong for STL containers - go for something like the boost shared_ptr.

Shane
A: 

You don't say why you want a collection of statically sized arrays of different sizes. This is rather odd. Why not use a collection of dynamically sized arrays?

Your choices are:

  • use std::vector instead of std::tr1::array.

  • Store a pointer to the array and the size of the array in your collection. This could look something like: std::vector<std::pair<int *, size_t> >. Just make sure that the lifetime of the arrays is at least as long as the lifetime of the vector!

  • I would expect boost::variant to work as well, but it would be rather fiddly to use in practice.

boost::variant<array<int, 2>, array<int, 4>, array<int, 6>, ... >

Richard Wolf
+2  A: 

I would use Boost's MPL and Fusion libraries. There are two ways of ending up with the type list: generate them, or explicitly define them. The former is bit more flexible, but it's hard to say which is right for you since we don't know how you get the values you have.

In any case, generating:

#include <boost/mpl/for_each.hpp>
#include <boost/mpl/range_c.hpp>
#include <boost/mpl/transform.hpp>
#include <boost/mpl/vector.hpp>
#include <array>
#include <iostream>

namespace bmpl = boost::mpl;

// turns an index into an array
template <typename T>
struct make_array
{
    // or whatever scheme you have
    static const std::size_t size = T::value * 2;

    // define generated type
    typedef std::array<int, size> type;
};

// list of values to convert
typedef bmpl::range_c<size_t, 1, 10> array_range;

// transform that list into arrays, into a vector
typedef bmpl::transform<array_range, make_array<bmpl::_1>,
                            bmpl::back_inserter<bmpl::vector<>>
                                >::type array_collection;

Or explicitly stating:

#include <boost/mpl/vector.hpp>
#include <array>
#include <iostream>

namespace bmpl = boost::mpl;

// list all array types
typedef bmpl::vector<
            std::array<int, 2>,
            std::array<int, 4>,
            std::array<int, 6>,
            std::array<int, 8>,
            std::array<int, 10>,
            std::array<int, 12>,
            std::array<int, 14>,
            std::array<int, 16>,
            std::array<int, 18>
                > array_collection;

Either way, you can then use it like this:

#include <boost/fusion/algorithm.hpp>
#include <boost/fusion/container/vector.hpp>
#include <boost/fusion/mpl.hpp>
#include <boost/fusion/sequence.hpp>
#include <boost/mpl/for_each.hpp>
#include <typeinfo>

// fusion "fuses" the bridge between MPL and runtime
namespace bf = boost::fusion;

struct print_type
{
    template <typename T>
    void operator()(const T&) const
    {
        std::cout << typeid(T).name() << "\n";
    }
};

struct print_values
{
    template <typename T>
    void operator()(const T& pArray) const
    {
        std::cout << "Printing array with size "
                    << pArray.size() << ":\n";
        std::for_each(pArray.begin(), pArray.end(),
                [](int pX)
                {
                    std::cout << pX <<  " ";
                });
        std::cout << std::endl;
    }
};

int main(void)
{
    // print all the types you have
    bmpl::for_each<array_collection>(print_type());
    std::cout.flush();

    // make a usable type out of the typelist
    typedef bf::result_of::as_vector<array_collection>::type array_fusion;
    array_fusion arrays; // now have an array of different arrays,
                         // compile-time generated but run-time usable

    // access like this:
    bf::at_c<0>(arrays)[1] = 5; 
    bf::at_c<1>(arrays)[2] = 7; 
    bf::at_c<2>(arrays)[0] = 135; 

    // for_each:
    bf::for_each(arrays, print_values());
}
GMan
MPL seems the solution :) Thanks.
AkiRoss