0

Suppose you have a class that operates on a vector:

class Foo{
public:
    Foo() {
        m_dynamic_data.push_back(5);
        std::cout << m_dynamic_data[0] << std::endl;
    }
private:
    std::vector<int> m_dynamic_data;
};

In my case this class is huge with 2500 additional lines of code. This class behaves dynamic (hence std::vector). But I would also like to provide a "static" implementation (using std::array). So std::size_t N is added, which now should control when to use which attribute.

template<std::size_t N>
class Foo{
private:
    std::vector<int> m_dynamic_data;  //use this, when N == 0
    std::array<int, N> m_static_data; //use this, when N != 0
};

I am not sure if I can get this to work. using #define won't do the job (since it can't alternate). constexpr can't be wrapped around two attributes either. The best solution is probably to provide a base class and then inherit the dynamic and static case from it. But before I spent the next days doing this, I wonder if there isn't a technique afterall.

I thought about putting both into a std::unique_ptr and only constructing the relevant array:

template<std::size_t N>
class Foo {
public:
    Foo() {
        if constexpr (N) {
            m_static_data_ptr = std::make_unique<std::array<int, N>>();
            (*m_static_data_ptr)[0] = 5;
            std::cout << (*m_static_data_ptr)[0] << std::endl;
        }
        else {
            m_dynamic_data_ptr = std::make_unique<std::vector<int>>(1);
            (*m_dynamic_data_ptr)[0] = 5;
            std::cout << (*m_dynamic_data_ptr)[0] << std::endl;
        }
    }
private:
    std::unique_ptr<std::vector<int>> m_dynamic_data_ptr;
    std::unique_ptr<std::array<int, N>> m_static_data_ptr;
};

I earlier asked about this case here. But apparently this doesn't seem like a good approach. (fragmenting memory, cache miss rate). std::optional also seems interesting, but it pushes the sizeof(Foo) too far for my goal.

Ultimately there is also using void pointers:

template<std::size_t N>
class Foo {
public:
    Foo() {
        if constexpr (N) {
            m_data = malloc(sizeof(std::array<int, N>));
            (*static_cast<std::array<int, N>*>(m_data))[0] = 5;
            std::cout << (*static_cast<std::array<int, N>*>(m_data))[0] << std::endl;
        }
        else {
            m_data = new std::vector<int>;
            (*static_cast<std::vector<int>*>(m_data)).push_back(5);
            std::cout << (*static_cast<std::vector<int>*>(m_data))[0] << std::endl;
        }
    }

    ~Foo() {
        delete[] m_data;
    }
private:
    void* m_data;
};

But this seems pretty dirty [...] So the goal would be to work with either array structure at compile time. Thanks for any help / suggestion!

ParadobC2
  • 72
  • 7
  • Template your class on both size and type of container? –  Aug 16 '18 at 21:26
  • 1
    You could use a `union` and just use `if (N) ...` to brach on using the vector or array. – NathanOliver Aug 16 '18 at 21:27
  • But I can't use `resize(...)` on `std::array` – ParadobC2 Aug 16 '18 at 21:28
  • Then put the code that does a `resize` in an `if constexpr` branch (not entirely sure if that works, but you can do something like that) – Justin Aug 16 '18 at 21:40
  • What do you mean by "alternate"? That word means that you'd switch every time you compile, like 1, 0, 1, 0, 1, 0, 1, 0... I think you probably want to be able to switch back and for depending on which one you need for given circumstances. Your subject says "at compile time," which makes it hard to understand why `#define` won't do the trick. If you want to be able to choose at run time, then set a flag when you create the object. – Caleb Aug 16 '18 at 21:48
  • What exactly do you mean by `push_back doesn't affect m_data`? – Axalo Aug 16 '18 at 21:49
  • @Justin but `std::array` has the size paramter, I can't seem to get this working while also templating for `std::vector` – ParadobC2 Aug 16 '18 at 21:52
  • @Axalo when running the code (creating a `Foo<0>` instance) it crashes *_Pnext was 0xCDCDCDD1.* I printed out `->size()` and it was `0` after `push_back` – ParadobC2 Aug 16 '18 at 21:54
  • @Caleb `#define` appears to "gate" only once. I tried it, it permanently exluded the other attribute. – ParadobC2 Aug 16 '18 at 21:56
  • 1
    @ParadobC2 That's probably because `malloc` doesn't actually create a `std::vector` object. You either need to use `new std::vector<...>` or [placement new](https://en.cppreference.com/w/cpp/language/new). – Axalo Aug 16 '18 at 21:56
  • @ParadobC2 Yes, using a #define would choose which storage scheme to use once, *at compile time*, which is what you say you want. If you want to be able to switch between the two schemes, you need to be able to select which one to use while the program is running, which means either creating two different classes or setting a flag when you instantiate, or something like that. But as written, we can't really tell what you want or what your problem is. – Caleb Aug 16 '18 at 21:59
  • @Caleb but this forbids me from creating a Foo<0> and a Foo<5> in the same environment! But I need both – ParadobC2 Aug 16 '18 at 22:01
  • @Axalo interesting, why does it work for `std::array` then? – ParadobC2 Aug 16 '18 at 22:14
  • Because `std::array` is a POD, see [here](https://stackoverflow.com/questions/3674247/is-stdarrayt-s-guaranteed-to-be-pod-if-t-is-pod), but don't rely on that, always construct your objects properly otherwise you get UB. – Axalo Aug 16 '18 at 22:17

3 Answers3

4

You can abstract the data part of Foo to another class template.

template<std::size_t N> struct FooData
{
   std::array<int, N> container;
}

template <> struct FooData<0>
{
   std::vector<int> container;
}

template<std::size_t N>
class Foo{
   private:
      using DataType = FooData<N>;
      DataType data;
};

You have to add member functions to FooData to support additional abstractions. The number of such functions and their interface depends on how differently you use the containers in Foo.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
2

R Sahu's answer is great, but you don't need to access the container indirectly through a struct.

template<std::size_t N>
struct FooData { using type = std::array<int, N>;};

template <>
struct FooData<0> { using type = std::vector<int>; };

template<std::size_t N>
using FooData_t = typename FooData<N>::type;

template<std::size_t N>
class Foo{
   private:
      FooData_t<N> data;
};

Alternatively, you can also use std::conditional_t:

template<std::size_t N>
class Foo{
   private:
      std::conditional_t<N==0, std::vector<int>, std::array<int, N>> data;
};
Not a real meerkat
  • 5,604
  • 1
  • 24
  • 55
  • I like this even more, because I don't have to add `.container` after each call to the variable. Thanks! Perfect – ParadobC2 Aug 16 '18 at 22:21
1

You may want to isolate this dynamic/static "morphing" from the rest of your giant 2500-lines Foo class. I can imagine a tiny wrapper around std::array to mimic the interface of std::vector. It can be used as a member of Foo. If the static capacity is set to the sentinel value 0, then it can be specialized to just derive from a real std::vector:

#include <cassert>
#include <cstddef>

#include <array>
#include <iostream>
#include <vector>

template<class value_type_, std::size_t capacity_>
struct StaticOrDynamic {
  using value_type = value_type_;
  static constexpr std::size_t capacity = capacity_;

  std::array<value_type, capacity> arr_{};
  std::size_t size_{0};

  constexpr void push_back(const value_type& x) {
    assert(size_ < capacity && "must not exceed capacity");
    arr_[size_++] = x;
  }

  constexpr const value_type_& at(std::size_t i) const {
    assert(i < size_ && "must be in [0, size)");
    return arr_[i];
  }

/* other members etc */
};

template<class value_type_>
struct StaticOrDynamic<value_type_, 0>// specialization for dynamic case
  : std::vector<value_type_>
{
  using std::vector<value_type_>::vector;
};

template<std::size_t capacity_>
struct Foo {
  static constexpr std::size_t capacity = capacity_;

  StaticOrDynamic<int, capacity> m_data_{};

  Foo() {// static version may be constexpr (without debug output)
    m_data_.push_back(5);
    std::cout << m_data_.at(0) << std::endl;
  }
};

int main() {
  Foo<5> static_foo{};
  Foo<0> dynamic_foo{};
}

A similar behavior (static/dynamic chosen by a template parameter) is offered in, e.g., the Eigen library. I do not know how it is implemented there.

Julius
  • 1,816
  • 10
  • 14