views:

225

answers:

9

Hello,

Essentially, the situation is as follows:

I have a class template (using one template parameter length of type int) and want to introduce a static array. This array should be of length length and contain the elements 1 to length.

The code looks as follows up to now:

template<int length>
class myClass{
    static int array[length];
};

Then I wanted to write a line for initalizing the array

// of course, the line below does not work as intended.
template<int length> int myClass<length>::array[length]={1,2, ..., length};

(How) can this be achieved?

+2  A: 

Use "static constructor" idiom.

// EDIT 2

#include <iostream>

template<int length>
class myClass {
public:
    typedef int ArrayType[length];

    static struct StaticData {
        ArrayType array;

        StaticData()
        {
            for (int i = 0; i < length; i++) array[i] = i;
        }
    }
    static_data;

    static ArrayType &array;
};

template<int length>
typename myClass<length>::StaticData myClass<length>::static_data;

template<int length>
typename myClass<length>::ArrayType &myClass<length>::array = myClass<length>::static_data.array;

int main(int argc, char** argv) {
    const int LEN = 5;
    for (int i = 0; i < LEN; i++) {
        std::cout << myClass<LEN>::array[i];
    }
}
adf88
"static constructor idiom" gives 0 results ;) Can't be much of an idiom.
Peter Alexander
http://www.google.com/search?q="static+constructor"+c%2B%2B
adf88
Sorry but this doesn't work for me under gcc-4.3, the static constructor never gets called... Besides the fact that we need `myClass<length>::StaticConstructor myClass<length>::static_constructor`.
phlipsy
Can we safely assume that array will be initialized before static_constructor? I'm not sure if static data members have a defined initialization order (as opposed to non-static members which do). It seems like they should be initialized in the order they are declared in the class definition, however...
@stinky472: Within the same translation unit, statics are instantiated in the order in which they appear. It's only between translation units that makes them undefined.
DeadMG
Yes this can't work that way. You need to ensure that `static_constructor` is always referenced. Otherwise when the user only reerences `array`, it won't be filled. You also have some bugs regarding template usage in your code. Please test your answer if you aren't sure about it.
Johannes Schaub - litb
It was just a concept. Anyway, I corrected the example and now it's working.
adf88
+5  A: 

You can't do that with C-style arrays because they don't have value semantics.

If you use something like std::tr1::array however then you could easily do what you want by initialising to a function result, or by using an iterator that generates those values.

Peter Alexander
+1. Need some sort of class here to do this.
Boost.assign is very helpful for initializing any sort of container: http://www.boost.org/doc/libs/1_43_0/libs/assign/doc/index.html
Space_C0wb0y
+1  A: 

You can write a wrapper class, but I'm sure there are cleaner solutions:

template <size_t length>
class array_init_1_to_n
{
    int array[length];

public:

    array_init_1_to_n()
    {
        for (int i = 0; i < length; ++i)
        {
            array[i] = i + 1;
        }
    }

    operator int*()
    {
        return array;
    }

    operator const int*() const
    {
        return array;
    }
};

template<size_t length>
class myClass{
    static array_init_1_to_n<length> array;
};
FredOverflow
A: 

embed a for loop in a static constructor that runs up to length, its basically the same as using the initializer:

for(int i = 0; i < length; i++)
    array[i] = i + 1;
Necrolis
+1  A: 

It seems tough. The closest approach that i can think of would be the following :

template<int length>
class myClass
{
  public:
    myClass()
    {
      static InitializeArray<length> initializeArray(&array);
    }
    template<int length>
    class InitializeArray
    {
    public:
      InitializeArray(int* array) 
      {
        for(int i = 0; i < length ; ++i)
        array[i] = i;
      }
    };
    static int array[length];
    static myClass instance;
};
template<int length> int myClass<length>::array[length];
template<int length> myClass myClass::instance;
Benoît
What happens if you don't instantiate myClass?
FredOverflow
Without the modification i just edited in, it would not work.
Benoît
A: 

Here is an example using Boost.MPL:

#include <cstddef>
#include <iostream>

#include <boost/mpl/range_c.hpp>
#include <boost/mpl/string.hpp>

template<std::size_t length>
struct myClass {
  static const std::size_t Length = length;
  typedef typename boost::mpl::c_str< boost::mpl::range_c<std::size_t, 1, length + 1> > Array;
};

int main() {
  // check whether the array really contains the indented values
  typedef myClass<10> test;
  for (std::size_t i = 0; i < test::Length; ++i) {
    std::cout << test::Array::value[i] << std::endl;
  }
}

Note that the array is larger than length; currently its size is fixed.

Philipp
A: 

You can use explicit template instantiation of an additional static member whose constructor takes care of filling out the entries:

template<int length>
class myClass{
public:
    static int array[length];

    typedef enum{LENGTH=length} size_;

    struct filler
    {
        filler(void)
        {
            for(int i=0;i<LENGTH;++i)
                array[i]=i+1;
        }
    };

    static filler fill_;
};

// of course, the line[s] below now do work as intended.
template<int length> 
int myClass<length>::array[length];

//static member definition
template<int length>
typename myClass<length>::filler myClass<length>::fill_;

//explicit template instantiation
template myClass<5>::filler myClass<5>::fill_;

int main(void)
{
    for(int i=0;i<myClass<5>::LENGTH;++i)
        cout<<myClass<5>::array[i]<<endl;

    return 0;
}

Or, since a similar (probably better) solution has been already shown above by Benoit, here's a template recursive version, just for fun:

//recursive version:
template<int length>
class myClass{
public:
    static int array[length];

    typedef enum{LENGTH=length} size_;

    static void do_fill(int* the_array)
    {
        the_array[LENGTH-1]=LENGTH;
        myClass<length-1>::do_fill(the_array);
    }

    struct filler
    {
        filler(void)
        {
            /*for(int i=0;i<LENGTH;++i)
                array[i]=i+1;*/
            do_fill(array);
        }
    };

    static filler fill_;
};

//explicit specialization to end the recursion
template<>
class myClass<1>{
public:
    static int array[1];

    typedef enum{LENGTH=1} size_;

    static void do_fill(int* the_array)
    {
        the_array[LENGTH-1]=LENGTH;
    }
};

//definition of the explicitly specialized version of the array
//to make the linker happy:
int myClass<1>::array[1];

// of course, the line below does not work as intended.
template<int length> 
int myClass<length>::array[length];

//static member definition
template<int length>
typename myClass<length>::filler myClass<length>::fill_;

//explicit template instantiation
template myClass<5>::filler myClass<5>::fill_;

int main(void)
{
    for(int i=0;i<myClass<5>::LENGTH;++i)
        cout<<myClass<5>::array[i]<<endl;

    return 0;
}

Now, different compilers support different levels of template recursion (and this technique is compiler expensive) so, careful..."Here Be Dragons" ;-)

Oh, one more thing, you don't need to redefine the array in the specialized version of myClass, so you can get rid of instantiating array[1]:

//explicit specialization to end the recursion
template<>
class myClass<1>{
public:
    typedef enum{LENGTH=1} size_;

    static void do_fill(int* the_array)
    {
        the_array[LENGTH-1]=LENGTH;
    }
};
blue scorpion
A: 

Can't you wrap the array in a static function, so for example,

template<int length>
class myClass {
    static int* myArray() {
        static bool initd = false;
        static int array[length];
        if(!initd) {
            for(int i=0; i<length; ++i) {
                array[i] = i+1;
            }
            initd = true;
        }
        return array;
    };
};

and then access it like,

myClass<4>::myArray()[2] = 42;

It will be initialised on first use, and on following accesses since initd is static, if(!initd) will be false and the initialisation step will be skipped.

tjm
+1  A: 

I think this only works in C++0x. In C++03 whatever you do - you will end up with a dynamically initialized array, and thus potentially have initialization order problems. The following C++0x code won't have such problems.

template<int...>
struct myArray;

template<int N, int ...Ns>
struct myArray<N, Ns...> : myArray<N-1, N, Ns...> { };

template<int ...Ns>
struct myArray<0, Ns...> {
    static int array[sizeof...(Ns)];
};

template<int ...Ns>
int myArray<0, Ns...>::array[sizeof...(Ns)] = { Ns... } ;

template<int length>
class myClass : myArray<length> {
    using myArray<length>::array;
};
Johannes Schaub - litb