我想将一个操纵器列表传递给一个函数,如下所示:
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
我现在应该放弃,还是有办法做到这一点?
答案 0 :(得分:7)
输出操纵器只是为某些os << m
实例化定义了basic_ostream
的任何类型。操纵器可以是一个函数(受operator<<
的{{1}}重载限制),但它也可以是定义自己的basic_ostream
的任何类型。因此,我们需要执行类型擦除以捕获operator<<
以进行适当的operator<<
实例化;最简单的方法是使用basic_ostream
和lambda:
std::function
答案 1 :(得分: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)});
}
答案 2 :(得分: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;
}
另一种方法是使用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;
}
答案 3 :(得分: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和obligatory Wand Box中都可以使用clang进行操作。