你可以将一个操作器传递给函数吗?

10

我想把一系列的操作器传递给一个函数,就像这样:

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

理想情况下,它将被称为类似以下代码的东西:

some_object.print({std::fixed, std::setprecision(2)}));

g++ 4.7.0说:

error: ‘std::smanip’ has not been declared

显然,smanip在标准中并没有被定义,而且C++11编译器不需要为操纵符提供显式名称。我尝试通过借用已知的操纵符来声明类型,就像这样:

typedef decltype(std::fixed) manip;

这导致了一系列新的错误信息,包括以下这个:

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

我现在就应该放弃吗,还是有办法可以做到?


1
操纵器通常是函数,因此它们没有共同的基类。 - Some programmer dude
1
你可以定义自己的包装类,其中包含一个流操作符,该操作符应用于操纵器和一个公共基类,并将其用于参数。 - jmetcalfe
2
如果你真的想做这个,你可能需要将你的函数写成可变参数模板(因为不同的操作器有不同的类型,其中大部分你都不知道)。即使如此,这也不是一件容易的事情(例如,你可能还需要使用std::bind来处理需要参数的操作器)。 - Jerry Coffin
@JerryCoffin 我相信可变参数函数模板也可以起作用。 - Angew is no longer proud of SO
@Angew:我以为那就是我说的。不管怎样,那就是我想说的。 - Jerry Coffin
显示剩余3条评论
4个回答

7

一个输出操作器只是任何一种对于某些basic_ostream实例化,定义了os << m的类型。 操作器可以是函数(受basic_ostreamoperator<<重载的约束),但它也可以是定义了自己的operator<<的任何类型。 因此,我们需要执行类型抹除来捕获适当的basic_ostream实例化中的operator<<;最简单的方法是使用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;
}

有趣的方法,但无法在g++-4.7上编译。 - Olaf Dietsche
@OlafDietsche clang-3.2很好;看起来是g++在处理函数通用引用时存在的一个BUG。我会看看是否能找到解决方法。 - ecatmur
@OlafDietsche我为g++添加了一个重载函数;它现在可以与g++-4.7.2一起正常工作了。 - ecatmur
谢谢!这是一个非常简单和直接的方法。 - Ben Kovitz

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

有了这个之后,只需要稍微调整一下命名空间,你的代码就可以正常运行:

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

我尝试了被接受的答案和这个。我认为这个更好:操作器可以存储在向量中,并且操作器的向量可以被存储和传递。由于某种原因,被接受的答案不允许这样做。 - Jeet

1
由于操作器是函数,它取决于它们的签名。这意味着,您可以创建具有相同签名的操作器向量。
例如:
#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;
}

另一种选择是使用lambda表达式:
#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;
}

1
你的 setprecision lambda 使用了 std::cout,但没有使用给定的参数。我猜这是因为 setprecision 是基于 basic_[io]stream 定义的。除此之外,我喜欢你的方法,+1。 - Olaf Dietsche

0

标准的C++17解决方案,可能基于std::tuple。这是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;

}

一个可以将元组保存在类中等方式… 在MSVC CL编译器版本19.14.26431.0和使用clang的obligatory Wand Box中均可正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接