Question

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?

Était-ce utile?

La solution

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;
}

Autres conseils

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)});
}

Since the manipulators are functions, it depends on their signature. That means, you can create vector of manipulators with the same signature.

For example :

#include <iomanip>
#include <vector>
#include <iostream>

typedef std::ios_base& (*manipF)( std::ios_base& );

std::vector< manipF > getManipulators()
{
    std::vector< manipF > m =
    {
        std::showpos,
        std::boolalpha
    };
    return m;
}

int main()
{
  auto m = getManipulators();

  for ( auto& it : m )
  {
    std::cout<<it;
  }
  std::cout<<"hi " << true << 5.55555f << std::endl;
}

The alternative is to use lambdas :

#include <iomanip>
#include <vector>
#include <iostream>
#include <functional>

typedef std::function< std::ios_base& ( std::ios_base& ) > manipF;

std::vector< manipF > getManipulators()
{
    std::vector< manipF > m =
    {
        std::showpos,
        std::boolalpha,
        [] ( std::ios_base& )->std::ios_base&
        {
          std::cout << std::setprecision( 2 );
          return std::cout;
        }
    };
    return m;
}

int main()
{
  auto m = getManipulators();

  for ( auto& it : m )
  {
    it(std::cout);
  }
  std::cout<<"hi " << true << 5.55555f << std::endl;
}

Standard C++17 solution, might be based on std::tuple. Here is the P.O.C.

int main()
{
// quick p.o.c.
auto ios_mask_keeper = [&](auto mask) {
    // keep tuple here
    static auto mask_ = mask;
    return mask_;
};

// make required ios mask and keep it
auto the_tuple = ios_mask_keeper(
    // please note we can mix ios formaters and iomanip-ulators
    std::make_tuple(std::boolalpha, std::fixed, std::setprecision(2) ) 
);

// apply iomanip's stored in a tuple
std::apply([&](auto & ...x) 
{
    // c++17 fold 
    (std::cout << ... << x);
}, the_tuple);

return 0;

}

One may keep the tuple in a class, etc .. Works in both MSVC CL compiler Version 19.14.26431.0, and the obligatory Wand Box using clang.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top