Подтвердить что ты не робот

Как печатать 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...

4b9b3361

Ответ 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
}