Как распечатать boost :: any в поток? - PullRequest
15 голосов
/ 12 июля 2010

У меня есть карта 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, затем double, затем к строке и т. Д., Каждый раз игнорируя ошибки и пытаясь привести, пока приведение не будет успешным, и я могу печатать как указанный тип.

Но должен ли быть более простой метод в Boost? Мне нужно что-то вроде обратного lexical_cast ...

Ответы [ 9 ]

29 голосов
/ 12 июля 2010

Вместо этого вы можете использовать boost::spirit::hold_any.Он определен здесь:

#include <boost/spirit/home/support/detail/hold_any.hpp>

и полностью совместим с boost::any.Этот класс имеет два отличия по сравнению с boost::any:

  • , он использует идиому оптимизации небольших объектов и пару других приемов оптимизации, что делает spirit::hold_any меньше и быстрее boost::any
  • он имеет определенные операторы потоковой передачи (operator<<() и operator>>()), позволяющие без труда вводить и выводить spirit::hold_any.

Единственное ограничение - вы не можете вводить впустой spirit::hold_any, но он должен содержать (возможно, созданный по умолчанию) экземпляр типа, который ожидается от ввода.

5 голосов
/ 24 июня 2016

Если вы можете изменить 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 голосов
/ 12 июля 2010

К сожалению, с any единственный способ - использовать метод type () , чтобы определить, что содержится в any , а затем привести его к * 1007. * any_cast . Очевидно, у вас должен быть включен RTTI, но вы, вероятно, уже сделали это, если используете any :

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;
  }
  ...
}
1 голос
/ 13 октября 2015

Определите некоторую вспомогательную функцию для вывода в поток:

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;
}
0 голосов
/ 15 февраля 2018

Немного поздно для этой вечеринки, но любой, кого это может заинтересовать, может также использовать std::tuple и std::for_each -подобный шаблон, который перебирает кортеж.

Это основано на ответе ingomueller.net в этой теме.

У меня был недавний случай, когда я создал карту свойств (считывая значения конфигурации, в основном, фундаментальные типы, из файла XML и вставляя их в std::unordered_map, где тип значениятипа any. В целях отладки я хотел иметь возможность распечатать всю карту с ее ключами и значениями вместе с типом значения.

В этом проекте я не использую Boost вообще, я использовал свою собственную any реализацию, но она очень похожа на boost :: any.

Оператор вставки в основном выглядит следующим образом:

template <typename TChar>
inline std::basic_ostream<TChar>&
operator<< (std::basic_ostream<TChar>& os, const sl::common::any& v)
{
    // Types that we support with sl::common::any.
    std::tuple<
        float, double, bool, 
        int8_t, uint8_t, 
        int16_t, uint16_t,
        int32_t, uint32_t, 
        int64_t, uint64_t,
        std::wstring, const wchar_t*,
        StreamInserter::UnsupportedType> t;

    // Prepare ostream for printing a value of type any
    StreamInserter si(os, v);

    // Iterate over all types in tuple t. If the last type(UnsupportedType) is
    // reached, given v is unsupported.
    for_each(t, si);
    return os;
}

шаблон for_eachвыглядит следующим образом (C ++ 14):

template <typename Tuple, typename F, std::size_t ...Indices>
constexpr void for_each_impl(Tuple&& tuple, F&& f, std::index_sequence<Indices...>) {
    using swallow = int[];
    (void)swallow{1,
        (f(std::get<Indices>(std::forward<Tuple>(tuple))), void(), int{})...
    };
}

template <typename Tuple, typename F>
constexpr void for_each(Tuple&& tuple, F&& f) {
    constexpr std::size_t N = std::tuple_size<std::remove_reference_t<Tuple>>::value;
    for_each_impl(std::forward<Tuple>(tuple), std::forward<F>(f),
                  std::make_index_sequence<N>{});
}

При этом просто используйте класс StreamInserter или что-то подобное, показанное в ответе Ingos.

Надеюсь, это поможет.

0 голосов
/ 16 июня 2017

Список переключателей типов, предложенный в других ответах, можно улучшить с помощью цикла над списком типов, используя 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
}
0 голосов
/ 08 мая 2015

Вместо того, чтобы переписывать мой класс для использования 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?

0 голосов
/ 22 декабря 2012

Попробуйте использовать xany https://sourceforge.net/projects/extendableany/?source=directory Класс xany позволяет добавлять новые методы к любой существующей функциональности.Кстати, в документации есть пример, который делает именно то, что вы хотите.

0 голосов
/ 12 июля 2010

Я думаю, что вы должны охватить каждый возможный случай объектов, которые вы должны напечатать ... Или использовать boost :: option.

РЕДАКТИРОВАТЬ: Извините, я думал, что напишу почему.

Причина, по которой я так думаю, заключается в том, что, глядя на любой исходный код, кажется, что он опирается на тот факт, что ВЫ предоставляете типы при вставке и получении данных. Когда вы вставляете, компилятор автоматически обнаруживает данные, поэтому вам не нужно их указывать. Но когда вы получаете данные, вы должны использовать any_cast, потому что вы не уверены, какой тип данных вы получаете.

Если бы он работал по-другому, и тип данных был уверен, я думаю, что в any_cast не было бы необходимости:)

Вместо этого вариант имеет ограниченный набор возможных типов данных, и эта информация несколько регистрируется, что дает вам возможность универсальным образом повторять контейнер вариантов.

Если вам нужна такая манипуляция - повторение общего набора значений - я думаю, вы должны использовать вариант.

...