Search code examples
c++std-variant

class with std::variant - custom [] operator based on variant content


I have a simple class:

#include <variant>

struct RawDataArray{
    std::variant<double*, float*> data;

    template <typename T>
    constexpr bool IsType() const noexcept{
        return std::holds_alternative<T*>(data);
    }

    template <typename T>
    T& operator [](const int index){
        return std::get<T*>(data)[index];       
    }
};

int main(){
   double* tmpData = new double[3];
   tmpData[0] = 1;
   tmpData[1] = 2;
   tmpData[2] = 3;

   RawDataArray rawData;
   rawData.data = tmpData;

   rawData[0] = 0.0;
}

However, I got:

error C2676: binary '[': 'RawDataArray' does not define this operator or a conversion to a type acceptable to the predefined operator
message : could be 'T &RawDataArray::operator [](const int)'
message : 'T &RawDataArray::operator [](const int)': could not deduce template argument for 'T'

I understand the error, but I don't know, how to write such a method. I thought that using 0.0 or 0.0f would auto-deduce the T. I also tried to specify variable double x = 0.0 and use this, with the same error.


Solution

  • You do this by making [] non-template, and returning a helper class with the right overloaded operators:

    #include <iostream>
    #include <variant>
    
    struct RawDataArray
    {
        std::variant<double *, float *> data;
    
        class ElemHelper
        {
            RawDataArray &target;
            std::size_t index = 0;
    
          public:
            ElemHelper(RawDataArray &target, std::size_t index) : target(target), index(index) {}
    
            // Those aren't strictly necessary, but help prevent some kinds of misuse.
            // Same for `&&` on the functions below.
            ElemHelper(const ElemHelper &) = delete;
            ElemHelper &operator=(const ElemHelper &) = delete;
    
            template <typename T>
            [[nodiscard]] operator T &() &&
            {
                return std::get<T *>(target.data)[index];
            }
    
            template <typename T>
            const T &operator=(const T &value) &&
            {
                return std::get<T *>(target.data)[index] = value;
            }
        };
    
        [[nodiscard]] ElemHelper operator [](std::size_t index)
        {
            return {*this, index};
        }
    };
    
    int main()
    {
        RawDataArray raw_data;
        raw_data.data = new double[3] {1,2,3};
    
        raw_data[0] = 0.0;
        std::cout << double(raw_data[0]) << '\n';
    }
    

    This also needs a const overload of operator[], with a different helper class that overloads operator const T & and nothing else. That's left as an exercise to the reader.