Я хотел бы передать список манипуляторов функции, что-то вроде этого:
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
Должен ли я просто сдаться сейчас, или есть способ сделать это?
Выходной манипулятор — это просто любой тип, для которого os << m
определяется для некоторых basic_ostream
конкретизации. Манипулятор может быть функцией (при условии operator<<
перегрузки basic_ostream
) но это также может быть любой тип, который определяет свой собственный operator<<
, Таким образом, мы должны выполнить стирание типа, чтобы захватить operator<<
для соответствующего basic_ostream
конкретизации; самый простой способ сделать это с std::function
и лямбда:
#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;
}
У ваших манипуляторов может быть довольно много произвольных типов, поэтому вы должны использовать шаблоны для их обработки. Чтобы получить к ним доступ с помощью указателя или ссылки фиксированного типа, вам придется использовать общий базовый класс для всех этих шаблонов. Этот тип полиморфизма работает только для указателей и ссылок, но вам, вероятно, нужна семантика значений, особенно для хранения их в контейнерах. Так что самый простой способ это позволить 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)});
}
Поскольку манипуляторы являются функциями, это зависит от их сигнатуры. Это означает, что вы можете создать вектор манипуляторов с одинаковой подписью.
Например :
#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;
}
Альтернатива — использовать лямбды:
#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;
}
Стандартное решение 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;
}
Можно хранить кортеж в классе и т. Д. Работает как в компиляторе CL MSVC версии 19.14.26431.0, так и в обязательный Wand Box используя лязг