2

I want to make an int32_le_read(x) manipulator that reads binary from a file and writes to x. I implemented the int32_bin_manip class

class bin_manip
{
    public:
     bin_manip();
     ~bin_manip();
     friend std::istream& operator>>(std::istream& is,bin_manip& obj);
     friend std::ostream& operator<<(std::ostream& os, const bin_manip& obj);
     virtual std::ostream& write(std::ostream& os) const = 0;
     virtual std::istream& read(std::istream& is) = 0;
};

class int32_bin_manip : public bin_manip
{
    public:
        int32_t* x;
        int32_bin_manip(int& x);
        std::ostream& write(std::ostream& os) const;
        std::istream& read(std::istream& is);
};

for which operator>> and operator<< exist. How do I make a manipulator for istream of it now? For the ostream manipulator, I just created a function that returns an instance of this class and everything worked fine.

int32_bin_manip write_le_int32(int& x)
{
    return int32_bin_manip(x);
}

But I can't do that with the istream manipulator, because its argument is int32_bin_manip& obj, unlike const int32_bin_manip& obj, which means I can't create a function that returns an instance of int32_bin_manip and use it like istream >> int32_le_read(x), because it will bind the temporary instance to a non-const reference which is forbidden. So how do I need to implement such a manipulator?

paleonix
  • 2,293
  • 1
  • 13
  • 29
Max Nov
  • 23
  • 2
  • These classes you are writing might be useful, but they are not manipulators. Manipulators don't read or write data, they modify the stream which is doing the reading or writing. – john Aug 13 '23 at 08:45
  • I don't see any reason you cannot change `friend std::istream& operator>>(std::istream& is,bin_manip& obj);` to `friend std::istream& operator>>(std::istream& is,const bin_manip& obj);`. Your 'manipulator' is `const` even though the data being read is not `const`. – john Aug 13 '23 at 08:47
  • @john Thank you! This worked with int32, but I have another manipulator, that works with string in C style (char*), and in this case I need to modify pointer to string (delete and allocate new space for it) that placed inside manipulator's class. – Max Nov Aug 13 '23 at 08:58
  • Well I guess you can pull the same trick, store a pointer to the pointer. At the very worst you can use `const_cast` (or even `mutable`). These options are OK as long as you don't use them to modify objects which are actually `const`. That would be undefined behaviour. – john Aug 13 '23 at 09:35

2 Answers2

3

You can't bind a non-const lvalue reference to a temporary, but you don't have to since you a not modifying the object itself but instead another object that it's pointing at, so make the operator>> take it by const& too:

class bin_manip {
public:
    virtual ~bin_manip() = default; // <- added
    friend std::istream& operator>>(std::istream& is, const bin_manip& obj);
    //                                                ^^^^^ added
    friend std::ostream& operator<<(std::ostream& os, const bin_manip& obj);

    virtual std::ostream& write(std::ostream& os) const = 0;
    virtual std::istream& read(std::istream& is) const = 0;
    //                                           ^^^^^ added
};

Demo

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • Thank you! I understood this, but I think this solution won't work if I will do the same with a c-style string, because in this case I need to reallocate space for pointer. Sorry, of course I should have written about this in the question. – Max Nov Aug 13 '23 at 09:02
  • @MaxNov Hmm, I don't really see what that'd look like. Perhaps you could add it to the question too? – Ted Lyngmo Aug 13 '23 at 09:04
  • I found way how to solve this, I think now it's not worth adding this to the original question, but I'll explain what the problem was. I wanted to make similar manipulator, which must work like this: istream >> read_c_str(s), where s is (char*). In this case I have to save pointer to s in my manipulator class, and when >> operator called, the pointer must erase and then create again with new length (delete s; s = new char[size_of_string]), because of it I cant mark read function as a const. – Max Nov Aug 13 '23 at 09:19
  • But I found another way, I just use s as a buffer and allocate enough space for it at the declaration of s, and after i just fill this string with new character from istream,without reallocation for this pointer @Ted Lyngmo – Max Nov Aug 13 '23 at 09:21
  • @MaxNov `delete` should be `delete[]` in that case. I would however not assume that the `char*` is pointing at anything. [example](https://godbolt.org/z/rcn1eY5M7) – Ted Lyngmo Aug 13 '23 at 09:21
  • @MaxNov ... and it'd be much nicer with `std::string`s instead: [example](https://godbolt.org/z/M698Eq788) – Ted Lyngmo Aug 13 '23 at 09:28
0

You could simply add another overload taking an rvalue reference to int32_bin_manip; rvalue references work as lvalue references to non-const inside the function body of a function with the rvalue reference as argument.

Note: write functionality removed for brevity in the following example, since there's no issue with using const in this case.

class bin_manip
{
public:
    virtual std::istream& read(std::istream& is) = 0;

    friend std::istream& operator>>(std::istream& is, bin_manip& obj)
    {
        obj.read(is);
        return is;
    }
};

class int32_bin_manip : public bin_manip
{
    int32_t& m_x;
public:
    constexpr int32_bin_manip(int32_t& x)
        : m_x(x)
    {
    }

    std::istream& read(std::istream& is) override
    {
        is >> m_x;
        return is;
    }

    friend std::istream& operator>>(std::istream& is, int32_bin_manip&& obj)
    {
        obj.read(is);
        return is;
    }
};

int main() {
    int32_t i;
    std::cin >> int32_bin_manip(i);
    std::cout << i << '\n';
}

Depending on your design you may want to introduce a function with 2 seperate overloads for read function calls on lvalues and rvalue references, if this suits your design better:

class bin_manip
{
public:
    virtual std::istream& read(std::istream& is) & = 0; // this one works just for lvalues, not for rvalue references

    friend std::istream& operator>>(std::istream& is, bin_manip& obj)
    {
        obj.read(is);
        return is;
    }
};

class rvalue_enabled_bin_manip : public bin_manip
{
public:

    using bin_manip::read;

    virtual std::istream& read(std::istream& is)&& // overload for rvalues
    {
        // fall back to standard, non-rvalue
        return read(is);
    }

    friend std::istream& operator>>(std::istream& is, rvalue_enabled_bin_manip&& obj)
    {
        std::move(obj).read(is);
        return is;
    }
};

class int32_bin_manip : public rvalue_enabled_bin_manip
{
    int32_t& m_x;
public:
    constexpr int32_bin_manip(int32_t& x)
        : m_x(x)
    {
    }

    std::istream& read(std::istream& is) & override
    {
        is >> m_x;
        return is;
    }
};
fabian
  • 80,457
  • 12
  • 86
  • 114