C++ boost::any для определения моей собственной печати,
Я изо всех сил пытаюсь найти, как сделать, чтобы использовать boost::any
создать функцию печати, которая может печатать любой тип, используя шаблон в первую очередь.
template <typename T>
struct printer {
void print(ostream& os, const boost::any& a);
};
Мне нужно сначала определить print()
, я хочу иметь реальный operator <<
для любого, идея проста: прикрепить к любому объекту экземпляр классаprinter<T>
с подходящим T и изменить этот объект, когда тип значения any
изменения. Первая техническая проблема заключается в том, что объект принтера зависит от T, тогда как любой из них не является (и не должен быть) шаблоном класса.
Пожалуйста, мне действительно нужна рука на сегодня или завтра, у меня есть срок на завтра, но я хочу поработать над этим вечером.
4 ответа
Существует довольно простой способ сделать это, описанный в статье " Помимо стандартной библиотеки C++: введение в повышение ":
struct streamer {
virtual void print(ostream &o, const boost::any &a) const =0;
virtual streamer * clone() const = 0;
virtual ~streamer() {}
};
template <class T>
struct streamer_impl: streamer{
void print(ostream &o, const boost::any &a) const { o << *boost::any_cast<T>(a); }
streamer *clone() const { return new streamer_impl<T>(); }
};
class any_out {
streamer *streamer_;
boost::any o_;
void swap(any_out & r){
std::swap(streamer_, r.streamer_);
std::swap(o_, r.o_);
}
public:
any_out(): streamer_(0) {}
template<class T> any_out(const T& value)
: streamer_(new streamer_impl<T>()), o_(value) {}
any_out(const any_out& a)
: streamer_(a.streamer_ ? a.streamer_->clone() : 0), o_(a.o_) {}
template <class T>
any_out & operator=(const T& r) {
any_out(r).swap(*this);
return *this;
}
~any_out() { delete streamer_; }
friend std::ostream &operator<<(std::ostream& o, const any_out & a) {
if(a.streamer_)
a.streamer_->print(o, a);
return o;
}
};
а затем вы используете any_out
вместо boost::any
,
Очень хороший ответ Павла Зубрицкого (с упоминанием книги Бьёрна Карлссона).
Но в коде есть несколько ошибок в следующих строках:
// ...
o << *boost::any_cast<T>(a); // should be: o << *boost::any_cast<T>(&a);
// ...
a.streamer_->print(o, a); // should be: a.streamer_->print(o, a.o_);
Вот исправленная версия ответа Павла Зубрицкого, которая работает (частично...)
#ifndef ANY_OUT_H
#define ANY_OUT_H
#include <iostream>
#include <boost/any.hpp>
struct streamer {
virtual void print(std::ostream &o, const boost::any &a) const =0;
virtual streamer * clone() const = 0;
virtual ~streamer() {}
};
template <class T>
struct streamer_impl: streamer{
void print(std::ostream &o, const boost::any &a) const { o << *boost::any_cast<T>(&a); }
streamer *clone() const { return new streamer_impl<T>(); }
};
class any_out {
streamer *streamer_;
boost::any o_;
void swap(any_out & r){
std::swap(streamer_, r.streamer_);
std::swap(o_, r.o_);
}
public:
any_out(): streamer_(0) {}
template<class T> any_out(const T& value)
: streamer_(new streamer_impl<T>()), o_(value) {}
any_out(const any_out& a)
: streamer_(a.streamer_ ? a.streamer_->clone() : 0), o_(a.o_) {}
template <class T>
any_out & operator=(const T& r) {
any_out(r).swap(*this);
return *this;
}
~any_out() { delete streamer_; }
friend std::ostream &operator<<(std::ostream& o, const any_out & a);
};
std::ostream &operator<<(std::ostream& o, const any_out & a) {
if(a.streamer_)
a.streamer_->print(o, a.o_);
return o;
}
#endif
Этот тест-код работает:
{
any_out a = 5;
std::cout << a << std::endl;
}
Тем не мение!!!!
Следующее не работает:
int main()
{
char str[] = "mystring";
any_out a = str;
std::cout << a << std::endl;
a = "myconststring";
std::cout << a << std::endl;
}
Здесь ничего не печатается.
Зачем??
Ну типа перепутано, в следующем конструкторе
any_out(const T& value)
если мы тогда создаем экземпляр Streamer как
new streamer_impl<T>()
Удаление ссылки из конструктора, т.е.
any_out(const T value)
... это одно решение.
Другое решение состоит в том, чтобы оставить ссылку и настроить экземпляр шаблона streamer_impl
, Увидеть ниже
Что приводит к следующему рекомендованному решению:
#ifndef ANY_OUT_H
#define ANY_OUT_H
#include <iostream>
#include <boost/any.hpp>
struct streamer {
virtual void print(std::ostream &o, const boost::any &a) const =0;
virtual streamer * clone() const = 0;
virtual ~streamer() {}
};
template <class T>
struct streamer_impl: streamer{
void print(std::ostream &o, const boost::any &a) const { o << boost::any_cast<T>(a); }
streamer *clone() const { return new streamer_impl<T>(); }
};
class any_out {
boost::any o_;
streamer *streamer_;
void swap(any_out & r){
std::swap(streamer_, r.streamer_);
std::swap(o_, r.o_);
}
public:
any_out(): streamer_(0) {}
template<class T> any_out(const T& value)
: o_(value),
#if 1
streamer_(new streamer_impl<typename std::decay<decltype(value)>::type>)
#else
streamer_((o_.type() == typeid(const char *))
? static_cast<streamer *>(new streamer_impl<const char *>)
: static_cast<streamer *>(new streamer_impl<T>))
#endif
{
}
// template<class T> any_out(const T value)
// : o_(value),
// streamer_(new streamer_impl<T>)
// {
// }
any_out(const any_out& a)
: o_(a.o_), streamer_(a.streamer_ ? a.streamer_->clone() : 0) {}
template <class T>
any_out & operator=(const T& r) {
any_out(r).swap(*this);
return *this;
}
~any_out() { delete streamer_; }
friend std::ostream &operator<<(std::ostream& o, const any_out & a);
};
std::ostream &operator<<(std::ostream& o, const any_out & a) {
if(a.streamer_)
a.streamer_->print(o, a.o_);
return o;
}
#endif
Тестовый код, который дал некоторые проблемы выше, теперь работает хорошо (с "рекомендуемым решением"):
int main()
{
char str[] = "mystring";
any_out a = str;
std::cout << a << std::endl;
a = "myconststring";
std::cout << a << std::endl;
}
Я делаю это так, что я считаю чистым и безопасным:
any_extension.hpp:
namespace cpputil
{
struct AnyWriter
{
/// Register a type with the AnyWriter.
/// @pre T must have an ostream << operator available somewhere
template<class T> static bool registerType()
{
return registeredTypes().emplace(std::type_index(typeid(T)),
std::bind(&AnyWriter::write<T>,
std::placeholders::_1,
std::placeholders::_2)).second;
}
/// Write any registred object to a stream
/// @pre Underlying type must have been registered with a call to AnyWriter::registerType<T>
/// @param os is reference to a std::ostream
/// @param anyObject is a reference to a boost::any
static void writeAny(std::ostream& os, const boost::any& anyObject);
private:
// A function object that converts an any to a type and streams it to an ostream
using WriteFunction = std::function<void (std::ostream&, const boost::any&)>;
// a map of typeinfo to WriteFunction
using RegisteredTypes = std::unordered_map<std::type_index, WriteFunction >;
// retrieve the WriteFunction map in a safe way
static RegisteredTypes& registeredTypes();
// Convert an any to a type, and write it to a stream
template<class T> static void write(std::ostream& os, const boost::any& anyObject) {
try {
const T& typedObject = boost::any_cast<const T&>(anyObject);
os << typedObject;
}
catch(boost::bad_any_cast& e) {
os << "<exception in conversion: " << e.what() << ">";
}
}
};
}
namespace std {
ostream& operator<<(ostream& os, const ::boost::any& anyObject);
}
any_extension.cpp:
#include "any_extension.h"
#include <string>
namespace cpputil {
namespace AnyWriterRegistration {
const bool stringRegistered = AnyWriter::registerType<std::string>();
const bool intRegistered = AnyWriter::registerType<int>();
const bool doubleRegistered = AnyWriter::registerType<double>();
}
AnyWriter::RegisteredTypes& AnyWriter::registeredTypes()
{
static RegisteredTypes _registrationMap;
return _registrationMap;
}
void AnyWriter::writeAny(std::ostream &os, const boost::any &anyObject)
{
auto registered = registeredTypes();
auto iFind = registered.find(anyObject.type());
if(iFind == registered.end()) {
os << "<unregistered type: " << anyObject.type().name() << ">";
}
else {
iFind->second(os, anyObject);
}
}
}
namespace std {
ostream& operator<<(ostream& os, const ::boost::any& anyObject)
{
if(anyObject.empty()) {
os << "<empty>";
}
else {
cpputil::AnyWriter::writeAny(os, anyObject);
}
return os;
}
}
Для любого типа, который вы хотите поддерживать, просто убедитесь, что AnyWriter::register() был вызван для его типа, и для него существует оператор<<.
Например:
any_test.cpp:
struct chicken {};
std::operator<<(std::ostream& os, const chicken& aChicken) {
os << "cluck!";
return os;
}
namespace {
const bool chickenRegistered = AnyWriter::register<Chicken>();
}
void chickenTest() {
boost::any animal = chicken();
std::cout << animal << std::endl;
}
вывод: ткни!
Проверьте эту тему в списке рассылки Boost: http://lists.boost.org/Archives/boost/2005/01/79232.php
У него есть несколько идей, некоторые из которых кажутся нормальными, а некоторые нет (для меня). В целом, хотя это кажется трудной задачей для общего выполнения, поскольку (как упоминалось в этой теме) некоторые типы никогда не будут доступны для ostream, но могут содержаться в boost::any
объект.