1

Is it possible for a class to declare an array which can be overridden with an array of a different length in a derived class? I know I could just use std::vector but it is for a performance critical stage of a game engine and since the lengths are known at compile time it seems like it should be possible to do it statically.

I am aiming for something like this, but without the impossible requirement of a member variable which is both virtual and static:

struct F {
  virtual static const size_t n;
  Signal[n] inputs;
  Signal getInput(size_t i)
  {
    if(i<n)
      return inputs[i];
  }
};
struct Binary : F {
  static const size_t n=2;
};
Ptolom
  • 13
  • 2
  • No. not possible, but you can make Base class a template with the array size and inherit it with your desired size. – Raildex Dec 10 '21 at 15:06
  • No. In C++ all types have to have constant size. – Marek R Dec 10 '21 at 15:06
  • 3
    There is no such thing as "virtual" member variables. How did you determine that `std::vector` isn't fast enough? – molbdnilo Dec 10 '21 at 15:06
  • Virtual member variables isn't allowed. And you use the wrong syntax for arrays. And you attempt to use the variable `n` before it's initialized. It seems you might need to spend some more time with [some good books](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list/388282#388282). – Some programmer dude Dec 10 '21 at 15:07
  • Real question is: what are you try to achieve? Isn't `std::vector` fulfill your requirements? – Marek R Dec 10 '21 at 15:07
  • 1
    Regarding vector not being fast enough, how did you measure that? You *have* measured it? – Some programmer dude Dec 10 '21 at 15:08
  • Op hasn't measured I guess. – Raildex Dec 10 '21 at 15:08
  • Make it templated? `template F { std::array inputs; }` then `struct Binary : public F<2> {}`, `struct Decimal : public F<2> {}` – Brandon Dec 10 '21 at 15:09
  • It may be possible by templating the base class, possibly using [CRTP](https://stackoverflow.com/questions/4173254/what-is-the-curiously-recurring-template-pattern-crtp) but it seems unlikely using an vector here would actually have any perceptible performance problem. In fact making your object huge by putting the data as a large member array may be worse for performance, if that array is too big. And templating `F` implies it would no longer be a homogeneous interface, it would no longer work as a base class if polymorphism is also expected (which `virtual` implies). – François Andrieux Dec 10 '21 at 15:13
  • `getInput()` contains an error, you need to return *something* from the function or never reach the end of the function at all (such as by throwing an exception). – François Andrieux Dec 10 '21 at 15:14
  • No i have not measured performance. I have looked at benchmarks which show std::vector is somewhat slower, so I would am trying to avoid it in a class which is used every frame for every active game object. – Ptolom Dec 10 '21 at 15:22
  • @François Andrieux The polymorphism issue is what stopped me using templates, though having tried the templated solution, it does seem to work with virutal methods in F, which surprised me – Ptolom Dec 10 '21 at 15:25
  • @Ptolom You've seen that `std::vector` is somewhat slower *than what* and *for which operation*? If `std::vector` compares slower than a specific alternative, for a specific purpose, than it is an error to simply label is as "slow" and to generally avoid it. The only truly meaningful benchmark is to measure the solutions in your context with optimizations enabled. This looks like a straight forward case of premature optimization. – François Andrieux Dec 10 '21 at 15:27
  • @Ptolom Polymorphism will work even if `F` is a template, but `F` won't act as a base class for various different derived implementations. You can work around that by having an additional non-template class from which `F` inherits which provides the homogeneous interface. – François Andrieux Dec 10 '21 at 15:28
  • If you want more performance without the extra logic in `std::vector` to handle its extra features, I would recommend `std::array`, which is a thin wrapper around an array that stores alongside it the size of the array. If you're not growing your array, `std::array` is probably the right choice even without performance considerations since it more directly represents your use case. Someone seeing `std::vector` has to wonder if it will grow elsewhere. Someone seeing `std::array` knows it will not. – user904963 Dec 10 '21 at 18:54

2 Answers2

1

When you say compile time think, template or constexpr. Like this :

#include <cassert>
#include <array>
#include <iostream>

//-----------------------------------------------------------------------------

struct Signal
{
    std::size_t id = ++s_id;
    static std::size_t s_id;
};

std::size_t Signal::s_id{ 0 };

//-----------------------------------------------------------------------------

struct FBase
{
    virtual std::size_t size() const = 0;
    virtual ~FBase() = default;

protected:
    FBase() = default;

};

//-----------------------------------------------------------------------------


template<std::size_t N>
struct F : 
    public FBase
{
    Signal inputs[N]{};

    Signal getInput(size_t i)
    {
        assert(i < N);
        return inputs[i];
    }

    std::size_t size() const override
    {
        return N;
    }
};

//-----------------------------------------------------------------------------

struct Binary : 
    public F<2ul>
{
};

struct Trinary :
    public F<3ul>
{
};

//-----------------------------------------------------------------------------

int main()
{
    Binary bin;
    Trinary tri;

    auto signal = bin.getInput(1ul);
    std::cout << signal.id << "\n";

    std::cout << tri.size();
    
    return 0;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • This solves my problem. I had discounted the idea of using templates because I was under the impression that a templated base class was incompatible with virtual functions, but having tested it, it does work in my particular case. Thank you. – Ptolom Dec 10 '21 at 15:28
  • @Ptolom Try adding `struct Trinary : public F<3ul>{};` and you will find that `Trinary` and `Binary` have no common base class. In this sense, it breaks polymorphism. Without the template, they would share a common base class. – François Andrieux Dec 10 '21 at 15:31
  • Aha. I knew I was missing something. I shall do as everyone suggests and measure performance with std::vector first before resorting to adding more complexity. – Ptolom Dec 10 '21 at 15:39
  • I made this example based on your input, no mention of virtual functions there. Updated example to add that to the mix as well ;) But yes measuring is always good, things like branch prediction/data locality usually have a big impact too – Pepijn Kramer Dec 10 '21 at 15:44
0

Maybe you can use a pointer to Signal in the base class and then each inherited class will initialize the pointer to an array of different size. This will overcome the problem on the class dimension. Just remember to declar virtual de destructor and delete the memory of the array manaully.

Marco Beninca
  • 605
  • 4
  • 15
  • If you use `std::vector` instead of manually allocating, you won't have to remember to delete it in the destructor. – HolyBlackCat Dec 10 '21 at 16:34