你能把操纵器传递给一个函数吗?

Ben*_*itz 10 c++ iostream iomanip c++11

我想将一个操纵器列表传递给一个函数,如下所示:

void print(const vector<std::smanip>& manips) {
  // ...
  for (auto m : manips)
    cout << m;
  // ...
}
Run Code Online (Sandbox Code Playgroud)

理想情况下,这将通过以下代码调用:

some_object.print({std::fixed, std::setprecision(2)}));
Run Code Online (Sandbox Code Playgroud)

g ++ 4.7.0说:

error: ‘std::smanip’ has not been declared
Run Code Online (Sandbox Code Playgroud)

显然,smanip标准中并没有真正定义,C++ 11编译器不需要为操纵器类型提供显式名称.我尝试通过从一个已知的操纵器中取出来声明一个类型,如下所示:

typedef decltype(std::fixed) manip;
Run Code Online (Sandbox Code Playgroud)

这打开了一系列新的错误消息,包括这一消息:

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
Run Code Online (Sandbox Code Playgroud)

我现在应该放弃,还是有办法做到这一点?

eca*_*mur 7

输出操纵器只是os << m为某些basic_ostream实例化定义的任何类型.操纵器可以是一个函数(受到operator<<重载的影响basic_ostream),但它也可以是任何定义它自己的类型operator<<.因此,我们需要执行类型擦除以捕获operator<<适当的basic_ostream实例化; 最简单的方法是使用std::function和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;
}
Run Code Online (Sandbox Code Playgroud)


MvG*_*MvG 6

你的操纵器可以有几乎任意的类型,所以你必须使用模板来处理它们.为了使用固定类型的指针或引用访问它们,您必须为所有这些模板使用公共基类.这种多态只适用于指针和引用,但您可能需要值语义,特别是将它们存储在容器中.因此,最简单的方法是让shared_ptr内存管理处理,并使用另一个类来隐藏用户的所有丑陋细节.

结果可能如下所示:

#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;
}
Run Code Online (Sandbox Code Playgroud)

有了这个,您的代码在命名空间稍有变化后就可以运行了:

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)});
}
Run Code Online (Sandbox Code Playgroud)