Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you pass a manipulator to a function?

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?

like image 980
Ben Kovitz Avatar asked Feb 05 '13 07:02

Ben Kovitz


2 Answers

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;
}
like image 153
ecatmur Avatar answered Sep 20 '22 08:09

ecatmur


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)});
}
like image 38
MvG Avatar answered Sep 21 '22 08:09

MvG