How to write my own manipulator?
Asked Answered
A

4

5

Let's suppose I want to write my own manipulator for input and output.

cin >> mymanip >> str;

or

cout << mymanip << str;

What I want that mymanip does is toggle case the caracters I read from input and assigns the result to one string.

So, if I type "QwErTy" I get "qWeRtY" in the string.

This is a very basic task with one function, but i want to learn more about manipulators.

Can someone give a clue?

Thank you.

Adamis answered 27/11, 2016 at 22:14 Comment(2)
take a look at #21358650 and #314470Hohenlohe
@payamsbr, the links don't answer my question. What I want to know is how to create a manipulator. I just mentioned toggle string as an example.Adamis
V
5

The way is a little tricky - but it can be done, you can add own manipulator for stream.

First, you need your toggle:

class toggle_t {};
constexpr toggle_t toggle;

Next - the version for ostream (the case for istream is very similar...):

After putting toggle to ostream - you need some special object:

struct toggled_ostream
{
    std::ostream& os;
};

inline toggled_ostream operator << (std::ostream& os, toggle_t)
{
    return { os };
}

Beware, that someone might put togglein wrong place: cout << toggle << 123 - so it should work for all other types as ordinary stream:

template <typename T>
std::ostream& operator << (toggled_ostream tos, const T& v)
{
    return tos.os << v;
}

So - for char types (like char, const char*, std::string) write your toggle overloads. I am giving you version for char - it shouldn't be a problem to write version for "longer" types:

std::ostream& operator << (toggled_ostream tos, char v)
{
    char c = std::isupper(v) ? std::tolower(v)
                             : std::islower(v) ? std::toupper(v) : v;
    return tos.os << c;
}

Working demo.

Viticulture answered 27/11, 2016 at 23:14 Comment(3)
What is the use of the empty class => class toogle_t {};? and in my compiler I get an error with constexprAdamis
1) Maybe you have old compiler (not c++11) - use const instead.2) this is just tag class 3) Yes it works for next input - you must repeat it every time you want to print someting: cout << toogle << c1 << toogle << c2 - some std manipulators work that - so this is nothing special about it...Viticulture
As I wrote - I left to you to write std::ostream& operator << (toogled_ostream tos, const char* str) and std::ostream& operator << (toogled_ostream tos, const std::string& str)Viticulture
B
6

All that a manipulator does is set the corresponding bits in the std::ios_base base class.

For example, the std::setprecision() manipulator simply invokes std::ios_base::precision(), on the manipulated stream.

The implementation of std::setprecision() is almost readable, in gcc's headers (a rarity, for a C++ library template implementation):

 inline _Setprecision  setprecision(int __n)
     { return { __n }; }

std::setprecision() returns an internal std::_Precision object. Then, a simple template overload for the >> (and the << operator, which is similar) operator, for the std::_Precision object, handles the rest of the magic:

 template<typename _CharT, typename _Traits>
    inline basic_istream<_CharT, _Traits>& 
    operator>>(basic_istream<_CharT, _Traits>& __is, _Setprecision __f)
    { 
      __is.precision(__f._M_n); 
      return __is; 
    }

In your case, there are no bits in the std::ios_base class that implement your desired input/output transformation. As such, a manipulator, per se, won't work here.

What you're trying to do requires a completely different, more complicated, approach:

  1. A custom subclass of std::[io]stream, that uses a custom subclass of std::streambuf.

  2. The std::streambuf subclass reads or writes from a chained stream, transforming the input or output as you've described.

  3. Reading or writing from the custom subclass ends up reading or writing from the chained stream, transforming the data accordingly.

Backslide answered 27/11, 2016 at 22:25 Comment(0)
V
5

The way is a little tricky - but it can be done, you can add own manipulator for stream.

First, you need your toggle:

class toggle_t {};
constexpr toggle_t toggle;

Next - the version for ostream (the case for istream is very similar...):

After putting toggle to ostream - you need some special object:

struct toggled_ostream
{
    std::ostream& os;
};

inline toggled_ostream operator << (std::ostream& os, toggle_t)
{
    return { os };
}

Beware, that someone might put togglein wrong place: cout << toggle << 123 - so it should work for all other types as ordinary stream:

template <typename T>
std::ostream& operator << (toggled_ostream tos, const T& v)
{
    return tos.os << v;
}

So - for char types (like char, const char*, std::string) write your toggle overloads. I am giving you version for char - it shouldn't be a problem to write version for "longer" types:

std::ostream& operator << (toggled_ostream tos, char v)
{
    char c = std::isupper(v) ? std::tolower(v)
                             : std::islower(v) ? std::toupper(v) : v;
    return tos.os << c;
}

Working demo.

Viticulture answered 27/11, 2016 at 23:14 Comment(3)
What is the use of the empty class => class toogle_t {};? and in my compiler I get an error with constexprAdamis
1) Maybe you have old compiler (not c++11) - use const instead.2) this is just tag class 3) Yes it works for next input - you must repeat it every time you want to print someting: cout << toogle << c1 << toogle << c2 - some std manipulators work that - so this is nothing special about it...Viticulture
As I wrote - I left to you to write std::ostream& operator << (toogled_ostream tos, const char* str) and std::ostream& operator << (toogled_ostream tos, const std::string& str)Viticulture
J
3

You cannot do that. What you can do instead are manipulators that would take the string as argument, i.e.

std::cout << toggle(str);
std::cin  >> toggle(str);

A manipulator is only ever so-called syntactic sugar, i.e. it can do things more conveniently than otherwise. For example,

std::cout << std::setw(5) << x <<;

will do the same as

std::cout.width(5);
std::cout << x;

but is more convenient as it allows to be chained together with other << operations.

Now, there is no formatting support of the thing you want (swap lower and upper case characters), and hence also no way to provide syntactic sugar for that.

However, if the manipulator can take your string as an argument, then of course, you can achieve what you want, implemented in the standard way of manipulator implementation. For example,

struct toggle_output
{ std::string const&str; }

inline toggle_output toggle(std::string const&str)
{ return {str}; }

inline std::ostream& operator<<(std::ostream&out, toggle_output const&t)
{
  for(auto c:t.str)
    if     (std::islower(c)) out<<std::toupper(c);
    else if(std::isupper(c)) out<<std::tolower(c);
    else                     out<<c;
  return out;
}

struct toggle_input
{ std::string &str; }

inline toggle_input toggle(std::string&str)
{ return {str}; }

inline std::istream& operator>>(std::istream&in, toggle_input &t)
{
  in >> t.str;
  for(auto&c:t.str)
    if     (std::islower(c)) c=std::toupper(c);
    else if(std::isupper(c)) c=std::tolower(c);
  return in;
}

You may also need (to avoid confusion)

inline std::ostream& operator<<(std::ostream&out, toggle_input const&t)
{ return out<<toggle_output(t.str); }
Jehius answered 27/11, 2016 at 22:27 Comment(0)
G
1

As other answers explain, manipulators simply mimic existing std::ios_base functionality.

There's a simple solution to your problem, though I'm not sure if this can be called a manipulator:

struct toggle_in_helper
{
    std::string & res;
};

toggle_in_helper toggle (std::string & res)
{
    return {res};
}

std::istream & operator >> (std::istream & in, toggle_in_helper h)
{
    in >> h.res;
    for (auto & c : h.res)
        // toggle the case of 'c'
        ;
    return in;
}

That is, we create a helper class toggle_in_helper with overloaded operator >> which does the job.

Gore answered 27/11, 2016 at 22:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.