Как печатать boost:: any в поток?
У меня есть карта std::map<std::string, boost::any>
, которая поступает из пакета boost::program_options
. Теперь я хотел бы распечатать содержимое этой карты:
for(po::variables_map::const_iterator it = vm.begin(); it != vm.end(); ++it) {
std::cerr << it->first << ": " << it->second << std::endl;
}
К сожалению, это невозможно, потому что boost::any
не имеет operator<<
.
Каков самый простой способ печати этой карты?
Я мог бы определить свой собственный оператор вывода для любого, который автоматически пытается отобразить каждый any
в int, затем дважды, затем строку и т.д., каждый раз игнорируя ошибки и стараясь отбросить до тех пор, пока кастинг не будет успешным, и я могу напечатайте как указанный тип.
Но в Boost должен быть более простой метод? Мне нужно что-то вроде обратного lexical_cast
...
Ответы
Ответ 1
Вместо этого вы можете использовать boost::spirit::hold_any
. Он определен здесь:
#include <boost/spirit/home/support/detail/hold_any.hpp>
и полностью совместим с boost::any
. Этот класс имеет два отличия по сравнению с boost::any
:
- он использует идиому оптимизации небольшого объекта и пару других оптимизационных трюков, делая
spirit::hold_any
меньше и быстрее, чем boost::any
- он имеет операторы потоковой передачи (
operator<<()
и operator>>()
), которые позволяют вводить и выводить a spirit::hold_any
без видимых результатов.
Единственное ограничение состоит в том, что вы не можете вводить пустой spirit::hold_any
, но для этого должен быть экземпляр (возможно, по умолчанию построенный) типа, ожидаемого от ввода.
Ответ 2
Если вы можете изменить boost::any
на другой тип, вы можете использовать Boost.TypeErasure. Если вы когда-либо хотели создать тип, похожий на any
, но только поддерживающий типы, которые поддерживают эти конкретные операции во время компиляции, то это только для вас.
#include <boost/type_erasure/operators.hpp>
#include <boost/type_erasure/any.hpp>
#include <boost/mpl/vector.hpp>
#include <random>
#include <iostream>
namespace te = boost::type_erasure;
typedef te::any<boost::mpl::vector<
te::copy_constructible<>,
te::destructible<>,
te::ostreamable<>
>> streamable_any;
int main()
{
streamable_any i(42);
streamable_any d(23.5);
std::mt19937 mt;
streamable_any r(mt);
std::cout << i << "\n" << d << "\n" << r << "\n";
}
Live On Coliru
Ответ 3
К сожалению, при любом единственный способ - использовать метод type(), чтобы определить, что содержится в любом, затем отбросить его с any_cast. Очевидно, что вы должны включить RTTI, но вы, вероятно, уже это сделали, если используете любой:
for(po::variables_map::const_iterator it = vm.begin(); it != vm.end(); ++it) {
if(typeid(float) == it->second.type()) {
std::cerr << it->first << ": " << any_cast<float>(it->second) << std::endl;
}
else if(typeid(int) == it->second.type()) {
std::cerr << it->first << ": " << any_cast<int>(it->second) << std::endl;
}
...
}
Ответ 4
Определите некоторую функцию aux для вывода в поток:
template<class T>
bool out_to_stream(std::ostream& os, const boost::any& any_value)
{
try {
T v = boost::any_cast<T>(any_value);
os << v;
return true;
} catch(boost:: bad_any_cast& e) {
return false;
}
}
Вы можете определить специальное форматирование для некоторых типов
template<>
bool out_to_stream<std::string>(std::ostream& os, const boost::any& any_value)
{
try {
std::string v(std::move(boost::any_cast<std::string>(any_value)));
os << "'" << v << "'";
return true;
} catch(boost:: bad_any_cast& e) {
return false;
}
}
или
template<>
bool out_to_stream<bool>(std::ostream& os, const boost::any& any_value)
{
try {
os << ((boost::any_cast<bool>(any_value))? "yes" : "no");
return true;
} catch(boost:: bad_any_cast& e) {
return false;
}
}
Затем определите оператор вывода для boost::any
, где вы перечислите все типы, которые хотите попытаться выполнить и вывести
std::ostream& operator<<(std::ostream& os, const boost::any& any_value)
{
//list all types you want to try
if(!out_to_stream<int>(os, any_value))
if(!out_to_stream<double>(os, any_value))
if(!out_to_stream<bool>(os, any_value))
if(!out_to_stream<std::string>(os, any_value))
os<<"{unknown}"; // all cast are failed, an unknown type of any
return os;
}
И затем для value_type:
std::ostream& operator<<(std::ostream& os, const boost::program_options::variable_value& cmdline_val)
{
if(cmdline_val.empty()){
os << "<empty>";
} else {
os<<cmdline_val.value();
if(cmdline_val.defaulted())
os << "(default)";
}
return os;
}
Ответ 5
Я думаю, вам нужно покрыть каждый возможный случай объектов, которые вы должны распечатать... Или используйте boost:: variant.
EDIT: Извините, я думал, что напишу ПОЧЕМУ.
Причина, по которой я думаю, это потому, что, глядя на любой исходный код, кажется, полагается на то, что вы предоставляете типы при вставке и получении данных. При вставке данные автоматически обнаруживаются компилятором, поэтому вам не нужно указывать его. Но когда вы получаете данные, вы должны использовать any_cast, потому что вы не уверены в том, какой тип данных вы получаете.
Если бы он работал по-другому, и тип данных был уверен, я думаю, что это не нужно для any_cast:)
Вместо этого вариант имеет ограниченный набор возможных типов данных, и эта информация несколько зарегистрирована, что дает вам возможность итерации общим вариантом контейнера вариантов.
Если вам нужна такая манипуляция - итерация общего набора значений - я думаю, вы должны использовать вариант.
Ответ 6
Попробуйте использовать xany https://sourceforge.net/projects/extendableany/?source=directory класс xany позволяет добавлять новые методы к любой существующей функциональности. Кстати, в документации есть пример, который делает именно то, что вы хотите.
Ответ 7
Вместо того, чтобы повторно писать мой класс для использования boost::spirit::hold_any
, я создал способ потока boost::any
, аналогичный тому, что предлагалось manifest, но просто в одном месте.
ostream& operator<<(ostream& _os, const boost::any& _any)
{
// only define simple type conversions
if (_any.type() == typeid(int))
_os << boost::any_cast<int>(_any);
/*any other types you use...*/
}
Скорее громоздкий, но он позволяет мне передавать переменную boost::any
в любом месте моего кода.
Как можно построить boost::spirit::hold_any
из boost:any
?
Ответ 8
Список переключателей типов, предложенных в других ответах, может быть улучшен с помощью цикла над списком типов с использованием Boost MPL (см. документацию mpl::for_each
и mpl::vector
). Следующий код определяет operator<<
для любого boost::any
, который указан в списке типов SupportedTypes
, и в противном случае генерирует исключение.
#include <stdexcept>
#include <iostream>
#include <string>
#include <cstdint>
#include <boost/any.hpp>
#include <boost/mpl/for_each.hpp>
#include <boost/mpl/vector.hpp>
class StreamInserter
{
private:
std::ostream& os_;
const boost::any &v_;
mutable bool has_printed_;
public:
struct UnsupportedType {};
StreamInserter(std::ostream& os, const boost::any &v)
: os_(os), v_(v), has_printed_(false) {}
template <typename T>
void operator()(const T&) const
{
if (!has_printed_ && v_.type() == typeid(T))
{
os_ << boost::any_cast<T>(v_);
has_printed_ = true;
}
}
void operator()(const UnsupportedType&) const
{
if (!has_printed_)
throw std::runtime_error("unsupported type");
}
};
std::ostream& operator<<(std::ostream& os, const boost::any& v)
{
typedef boost::mpl::vector<float, double, int8_t, uint8_t, int16_t, uint16_t,
int32_t, uint32_t, int64_t, uint64_t, std::string, const char*,
StreamInserter::UnsupportedType> SupportedTypes;
StreamInserter si(os, v);
boost::mpl::for_each<SupportedTypes>(si);
return os;
}
int main(int, char**)
{
std::cout << boost::any(42.0) << std::endl;
std::cout << boost::any(42) << std::endl;
std::cout << boost::any(42UL) << std::endl;
std::cout << boost::any("42") << std::endl;
std::cout << boost::any(std::string("42")) << std::endl;
std::cout << boost::any(bool(42)) << std::endl; // throws exception
}