0

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.

Martin Perry
  • 9,232
  • 8
  • 46
  • 114
  • 2
    Template argument deduction is limited to fairly specific circumstances in C++, unlike type inference in a language like Haskell, which is much more general (and *could* infer the `T` in this example). Unfortunately your use case does not fall into those situations. – Silvio Mayolo Apr 07 '23 at 17:54
  • 2
    You'd need to say something like `rawData.operator[](0) = 0.0;` for type inference to work properly, which sort of defeats the purpose. – AndyG Apr 07 '23 at 18:06
  • This is the classic 'I want the return type of a function determined by something known only at runtime' problem, which C++ does not support. – Paul Sanders Apr 07 '23 at 18:08
  • Variants work better with inverting control a bit. Instead of requesting the data from it and then manipulating the data, you pass your manipulation in (via `visit()`) and let it do the work. – AndyG Apr 07 '23 at 18:09

2 Answers2

1

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.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
0

From operator[] return an object with overloaded operator= .

#include <variant>
#include <vector>

struct RawDataArray{
    std::variant<std::vector<double>, std::vector<float>> data;
    template<typename T>
    std::vector<T> get() {
        return std::get<std::vector<T>>(data);
    }
    struct Element {
        RawDataArray &p;
        std::size_t idx;
        auto operator=(const double& d) {
            p.get<double>()[idx] = d;
            return this;
        }
        template<typename T>
        auto to() {
            return p.get<T>()[idx];
        }
    };
    auto operator[](const std::size_t index){
        return Element{*this, index};
    }
};

int main(){
   std::vector<double> tmpData(3);
   tmpData[0] = 1;
   tmpData[1] = 2;
   tmpData[2] = 3;
   RawDataArray rawData;
   rawData.data = tmpData;
   rawData[0] = 0.0;
}
KamilCuk
  • 120,984
  • 8
  • 59
  • 111