I'd like to pass a list of manipulators to a function, something like this:
void print(const vector<std::smanip>& manips) {
// ...
for (auto m : manips)
cout << m;
// ...
}
which would ideally be called by code something like this:
some_object.print({std::fixed, std::setprecision(2)}));
g++ 4.7.0 says:
error: ‘std::smanip’ has not been declared
Apparently, smanip
isn't really defined in the standard, and C++11 compilers don't need to provide an explicit name for the type of manipulators. I tried declaring a type by leeching off of a known manipulator, like this:
typedef decltype(std::fixed) manip;
This opened up a host of new error messages, including this one:
error: ‘const _Tp* __gnu_cxx::new_allocator< <template-parameter-1-1>
>::address(__gnu_cxx::new_allocator< <template-parameter-1-1> >::const_reference)
const [with _Tp = std::ios_base&(std::ios_base&); __gnu_cxx::new_allocator<
<template-parameter-1-1> >::const_pointer = std::ios_base& (*)(std::ios_base&);
__gnu_cxx::new_allocator< <template-parameter-1-1> >::const_reference =
std::ios_base& (&)(std::ios_base&)]’ cannot be overloaded
Should I just give up now, or is there a way to do this?
An output manipulator is simply any type for which os << m
is defined for some basic_ostream
instantiation. A manipulator can be a function (subject to the operator<<
overloads of basic_ostream
) but it can also be any type which defines its own operator<<
. As such we need to perform type erasure to capture the operator<<
for an appropriate basic_ostream
instantiation; the simplest way to do this is with std::function
and a lambda:
#include <iostream>
#include <iomanip>
#include <functional>
#include <vector>
template<typename S>
struct out_manipulator: public std::function<S &(S &)> {
template<typename T> out_manipulator(T &&t): std::function<S &(S &)>(
[=](S &i) -> S &{ return i << t; }) {}
template<typename T> out_manipulator(T *t): std::function<S &(S &)>(
[=](S &i) -> S &{ return i << t; }) {} // for g++
template<typename U> friend U &operator<<(U &u, out_manipulator &a) {
return static_cast<U &>(a(u));
}
};
void print(const std::vector<out_manipulator<std::ostream>> &manips) {
for (auto m: manips)
std::cout << m;
}
int main() {
print({std::fixed, std::setprecision(2)});
std::cout << 3.14159;
}
Your manipulators can have pretty much arbitrary types, so you have to use templates to handle them. In order to access them using a pointer or reference of fixed type, you'll have to use a common base class for all these templates. This kind of polymorphism only works for pointers and references, but you probably want value semantics, particularly for storing these in containes. So the easiest way is letting a shared_ptr
take care of memory management, and using another class to hide all the ugly details from the user.
The result could look like this:
#include <memory>
#include <iostream>
// an abstract class to provide a common interface to all manipulators
class abstract_manip {
public:
virtual ~abstract_manip() { }
virtual void apply(std::ostream& out) const = 0;
};
// a wrapper template to let arbitrary manipulators follow that interface
template<typename M> class concrete_manip : public abstract_manip {
public:
concrete_manip(const M& manip) : _manip(manip) { }
void apply(std::ostream& out) const { out << _manip; }
private:
M _manip;
};
// a class to hide the memory management required for polymorphism
class smanip {
public:
template<typename M> smanip(const M& manip)
: _manip(new concrete_manip<M>(manip)) { }
template<typename R, typename A> smanip(R (&manip)(A))
: _manip(new concrete_manip<R (*)(A)>(&manip)) { }
void apply(std::ostream& out) const { _manip->apply(out); }
private:
std::shared_ptr<abstract_manip> _manip;
};
inline std::ostream& operator<<(std::ostream& out, const smanip& manip) {
manip.apply(out);
return out;
}
With this, your code works after some slight changes to namespaces:
void print(const std::vector<smanip>& manips) {
for (auto m : manips)
std::cout << m;
}
int main(int argc, const char** argv) {
print({std::fixed, std::setprecision(2)});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With