Wagner Gascho
Wagner Gascho

Reputation: 33

Filling N sized array with constexpr c++11

I'm aware that my code should work in c++14 but I have to replicate this behaviour in c++11, I havent been able to make a equivalent init() could anyone help?

enum MyEnum {
    BANANA, APPLE, PINEAPPLE, ORANGE, FRUIT_AMOUNT
};

template<MyEnum>
struct Fruit{
    virtual ~Fruit(){}
    virtual void dostuff(){}
};

template <>
struct Fruit<ORANGE>{
    void dostuff(){ cout<<"Hey apple!"<<endl;
}

constexpr array< Fruit*, FRUIT_AMOUNT > init(){   
    array< Fruit*, FRUIT_AMOUNT > myArray;
    for(int i =0; i < FRUIT_AMOUNT; i++)
        myArray[i] = new Fruit< (MyEnum) i >();

    return myArray;     
}

array<Fruit*, FRUIT_AMOUNT> myPrettyFruits = init();

Upvotes: 3

Views: 130

Answers (1)

max66
max66

Reputation: 66230

I'm aware that my code should work in c++14

Ehmm... not at all.

Your code has a lot of problems.

Some of them, in no particular order

(1) You can't write

array<Fruit*, FRUIT_AMOUNT>

because Fruit isn't a type; it's a template class.

So, by example, Fruit<BANANA> is a type, and you can write

std::array<Fruit<BANANA> *, FRUIT_AMOUNT>

but you can't have a pointer to Fruit because Fruit (without explicating a template argument) isn't a type.

A possible solution for this problem is make all Fruit types inheriting from a common base; by example

struct FruitBase
 { };

template <MyEnum>
struct Fruit : public FruitBase
 {
   virtual ~Fruit () {}
   virtual void dostuff () {}
 };

This way you can have an array of FruitBase pointers

std::array<FruitBase *, FRUIT_AMOUNT>

So you can put in the array pointer to Fruit<Something> types that are also FruitBase pointers.

(2) You can't have

Fruit< (MyEnum) i >

where i is a run-time known variable, because a template argument must be known compile time.

A possible C++14 solution is use std::make_index_sequence to get a sequence of template (so compile-time known) std::size_t values.

I suggest something as follows

template <std::size_t ... Is>
constexpr std::array<FruitBase *, FRUIT_AMOUNT>
   init_helper (std::index_sequence<Is...> const &)
 { return { { (FruitBase*)(new Fruit<(MyEnum)Is>()) ... } }; }

constexpr std::array<FruitBase *, FRUIT_AMOUNT> init ()
 { return init_helper(std::make_index_sequence<FRUIT_AMOUNT>{}); }

Observe that both functions are a single return statement; and this is necessary for a constexpr C++11 function.

Unfortunately std::index_sequence and std::make_index_sequence are available only starting from C++14. But it's possible make a C++11 substitute for they.

(3) new Something{} can't be executed compile-time

So you can define init_helper() constexpr but it's a fake constexpr function (so also init() is a fake constexpr function) because can't be executed compile time.

So you can write

std::array<FruitBase *, FRUIT_AMOUNT> myPrettyFruits = init();

but myPrettyFruits is initialized run-time.

If you try to initialize it compile-time

constexpr std::array<FruitBase *, FRUIT_AMOUNT> myPrettyFruits = init();

you get a compilation error.

The following is a full compiling C++11 example, with a std::index_sequence/std::make_index_sequence replacement, that works only run-time

#include <array>
#include <iostream>

template <std::size_t...>
struct indexSequence
 { using type = indexSequence; };

template <typename, typename>
struct concatSequences;

template <std::size_t... S1, std::size_t... S2>
struct concatSequences<indexSequence<S1...>, indexSequence<S2...>>
   : public indexSequence<S1..., ( sizeof...(S1) + S2 )...>
 { };

template <std::size_t N>
struct makeIndexSequenceH
   : public concatSequences<
               typename makeIndexSequenceH<(N>>1)>::type,
               typename makeIndexSequenceH<N-(N>>1)>::type>::type
 { };

template<>
struct makeIndexSequenceH<0> : public indexSequence<>
 { };

template<>
struct makeIndexSequenceH<1> : public indexSequence<0>
 { };

template <std::size_t N>
using makeIndexSequence = typename makeIndexSequenceH<N>::type;


enum MyEnum
 { BANANA, APPLE, PINEAPPLE, ORANGE, FRUIT_AMOUNT };

struct FruitBase
 { };

template <MyEnum>
struct Fruit : public FruitBase
 {
   virtual ~Fruit () {}
   virtual void dostuff () {}
 };

template <>
struct Fruit<ORANGE> : public FruitBase
 { void dostuff () { std::cout << "Hey apple!" << std::endl; } };

// fake constexpr function: new can't be executed compile-time
template <std::size_t ... Is>
constexpr std::array<FruitBase *, FRUIT_AMOUNT>
      init_helper (indexSequence<Is...> const &)
 { return { { (FruitBase*)(new Fruit<(MyEnum)Is>()) ... } }; }

// fake constexpr: init_helper() can't be executed compile-time
constexpr std::array<FruitBase *, FRUIT_AMOUNT> init ()
 { return init_helper(makeIndexSequence<FRUIT_AMOUNT>{}); }

int main ()
 {
   // compile (executed run-time)
   std::array<FruitBase *, FRUIT_AMOUNT> myPrettyFruits = init();

   // compilation error (init() can't be executed compile-time)
   //constexpr std::array<FruitBase *, FRUIT_AMOUNT> myPrettyFruits = init();

 }

Upvotes: 2

Related Questions