Search code examples
c++constantsiomanip

How to type custom io manipulators for use with const objects?


I'm trying to lift a simple Array-io manipulator I wrote to use std::vectors. Here is the old signature I had been using:

template<typename T>
struct arr {
   const size_t size;
   T* values;
   arr(const size_t size, T* values) : size(size), values(values) {};

   friend std::ostream& operator<<(std::ostream& os, const arr<T>& array);

   friend std::istream& operator>>(std::istream& is, arr<T>& array);
};

Now I tried to lift it like so:

template<typename T>
struct arr {
   std::vector<T>& vec;
   arr(std::vector<T>& vec) : vec(vec) {};

   friend std::ostream& operator<<(std::ostream& os, const arr<T>& array);

   friend std::istream& operator>>(std::istream& is, arr<T>& array);
};

However I am facing the following problem: I want to use << arr(member) from inside a const declared member method. This doesnt't compile of course:

error: binding reference of type ‘std::vector<std::unique_ptr<IController> >&’ to ‘const std::vector<std::unique_ptr<IController> >’ discards qualifiers

However when I change the constructor aguments and arr::vec to const std::vector<T>& I have the opposite problem and >> arr(member) cannot work anymore!

I was hoping that by initializing the arr instance as const I could solve this but I face the same compiler error with the following line:

const streamutils::arr<int> list(myVector);

How can I solve this without declaring two different types for the in and out directions? I attempted to look at the libstdc++ source to see how it is done for std::quoted but I couldn't figure it out.


Solution

  • You can parametrize your manipulator not with T but with vector<T> itself. This way you'll be able not to bother about your vector being const or not. Also create a helper function which returns the instance of your class with corresponding template type.

    template<typename T>
    using is_vector = std::is_same<T, std::vector<typename T::value_type, typename T::allocator_type>>;
    
    template<typename T>
    struct Arr {
        static_assert(is_vector<std::decay_t<T>>::value);
    
        T& vec;
    
        // Note that arr is passed by value here because it is a temporary
        // in expressions like 'cin >> arr(a)'
        template<typename U>
        friend std::enable_if_t<!std::is_const_v<U>, std::istream&> operator>>(std::istream& in, Arr<U> Arr);
    
        template<typename U>
        friend std::ostream& operator<<(std::ostream& out, const Arr<U>& Arr);
    };
    
    template<typename T>
    std::enable_if_t<!std::is_const_v<T>, std::istream&> operator>>(std::istream& in, Arr<T> arr) {
        int n;
        in >> n;
        arr.vec.resize(n);
        for (int i = 0; i < n; ++i) {
            in >> arr.vec[i];
        }
        return in;
    }
    
    template<typename T>
    std::ostream& operator<<(std::ostream& out, const Arr<T>& arr) {
        out << arr.vec.size() << "\n";
        for (const auto& x: arr.vec) {
            out << x << " ";
        }
        out << "\n";
        return out;
    }
    
    template<typename T, typename = typename is_vector<std::decay_t<T>>::type>
    Arr<T> arr(T& t)
    {
        return Arr<T>{t};
    }
    
    int main() {
        vector<int> a;
        cin >> arr(a);
        cout << arr(a) << endl;
    
        const vector<int> b{1, 2, 3};
        cin >> arr(b); // compile error
        cout << arr(b) << endl;
    }
    

    Also, consider reading this post, it explains various ways to make friend template operators (the one I showed here is not the best and not the only possible).