0

I am attempting to figure out how to achieve runtime polymorphism with a class called Signal, where its data type is passed as enum value to the constructor. Compile-time polymorphism with templates is not suitable.

As all values in the "signal" are supposed to be of same type, I chose variant-of-vectors instead of vector-of-variants.

enum class Type { UINT8, INT16, FLOAT };

using VariantVector = std::variant<std::vector<uint8_t>, std::vector<int16_t>, std::vector<float>>;

class Signal {
  public:  
    explicit Signal(size_t size, Type type, double value) 
      : m_size(size), m_type(type) {
        // Create variant vector of said type and initialize it with value
    }

    // Various methods ...

  private:
    size_t m_size;
    Type m_type;
    VariantVector m_data; // Variant of vectors
};

I have been able to make most of the things work, but got stuck on how to override the iterator methods. In order to use range-based loops as

Signal signal(100, Type::UINT8, 10);

for(auto& item : signal){
    // Do stuff with item
}

the begin() and end() functions need to be defined. I managed to partially solve this with explicit template functions as

template <typename T>
std::vector<T>::iterator begin();
template <typename T>
std::vector<T>::const_iterator begin() const;
template <typename T>
std::vector<T>::iterator end();
template <typename T>
std::vector<T>::const_iterator end() const;

but this does not work for range-based loops (and I'd like to avoid explicit templates anyway).

I tried to implement a couple solutions with std::visit, but did not get far - most solutions were based on operator overloading. Also looked into the double-dispatch visitor pattern, but don't really see how to apply it here.

How to achieve this with a variant of vectors?
Is this even possible or am I attempting to bend some rules of C++ wizardry?

Any insight is more than welcome. Thanks.

EDIT #1:
@Wyck asked in the comments how I'd like range-based loop to work. Ideally I'd like a situation as:

{
  Signal signal(100, Type::UINT8, 10);
  for(auto& item : signal){
      // item is of type uint8&
  }
}
{
  Signal signal(100, Type::FLOAT, 10);
  for(auto& item : signal){
      // item is of type float&
  }
}

Note the reference to the type, which means changing it should modify the private variant contents of Signal as well.

  • 2
    How do you want `for (auto& item: signal)` to work? Are you hoping it would declare `item` as an iterator of `variant` presumably? Otherwise you'd need a `std::get` in there somehow. That's because `variant` can hold different types at runtime so it can't know the type at compile time. – Wyck Jun 21 '22 at 16:04
  • 1
    I would use `std::visit`, with a visitor that range-iterates over the visited variant member. It'll likely generate quite a bit of code-bloat, but RAM is cheap. – Sam Varshavchik Jun 21 '22 at 16:39
  • You are hoping that you can pass a parameter at run time, and that would somehow magically affect the type of `item` at compile time. That can't possibly happen. For this to have any chance of working, `Signal` would have to be a template taking element type as a parameter. If for some reason you can't make it so, then you can't have a strongly typed iteration for the same reason. – Igor Tandetnik Jun 23 '22 at 23:01

0 Answers0