Построение boost::options из карты string/boost::any

У меня есть карта, которая представляет конфигурацию. Это карта std::string и boost::any.

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

Что бы я хотел сделать, так это построить параметры программы из этой карты, используя метод options_description::add_option(). Однако он принимает аргумент шаблона po::value<>, тогда как у меня есть только boost::any.

Пока что у меня есть только оболочка кода. m_Config представляет мой класс конфигурации, а getTuples() возвращает std::map<std::string, Tuple>. TuplePair является определением типа std::pair<std::string, Tuple>, а кортеж содержит интересующий меня boost::any.

    po::options_description desc;
    std::for_each(m_Config.getTuples().begin(),
                  m_Config.getTuples().end(),
                  [&desc](const TuplePair& _pair)
    {
            // what goes here? :)
            // desc.add_options() ( _pair.first, po::value<???>, "");
    });

Есть ли способ построить его таким образом, или мне нужно прибегнуть к этому самому?

Заранее спасибо!


person Moo-Juice    schedule 25.05.2011    source источник
comment
Разве вы не можете просто назначить string всем вашим параметрам, поскольку вы используете boost::any? Затем позже конвертировать по мере необходимости? Я не вижу другого способа, если вы также не сохраните предпочтительный тип для этого аргумента.   -  person RedX    schedule 25.05.2011


Ответы (2)


boost::any не относится к вашей проблеме. Он выполняет самую простую форму стирания типов: хранение и (типобезопасный) поиск, вот и все. Как вы видели, никакие другие операции не могут быть выполнены. Как указывает jhasse, вы можете просто протестировать каждый тип, который хотите поддерживать, но это кошмар обслуживания.

Лучше было бы расширить идею, которую использует boost::any. К сожалению, для этого требуется немного шаблонного кода. Если вы хотите попробовать это, в списке рассылки прямо сейчас обсуждается новая библиотека Boost (под названием «[boost] RFC: стирание типов»), которая по сути представляет собой утилиту стирания обобщенного типа: вы определяете операции, которые вы бы как ваш стертый тип для поддержки, и он генерирует правильный тип утилиты. (Например, он может имитировать boost::any, требуя, чтобы стертый тип был допускающим копирование и типобезопасным, и может имитировать boost::function<>, дополнительно требуя, чтобы тип был вызываемым.)

Однако, помимо этого, ваш лучший вариант, вероятно, — написать такой тип самостоятельно. Я сделаю это за вас:

#include <boost/program_options.hpp>
#include <typeinfo>
#include <stdexcept>

namespace po = boost::program_options;

class any_option
{
public: 
    any_option() :
    mContent(0) // no content
    {}

    template <typename T>
    any_option(const T& value) :
    mContent(new holder<T>(value))
    {
        // above is where the erasure happens,
        // holder<T> inherits from our non-template
        // base class, which will make virtual calls
        // to the actual implementation; see below
    }

    any_option(const any_option& other) :
    mContent(other.empty() ? 0 : other.mContent->clone())
    {
        // note we need an explicit clone method to copy,
        // since with an erased type it's impossible
    }

    any_option& operator=(any_option other)
    {
        // copy-and-swap idiom is short and sweet
        swap(*this, other);

        return *this;
    }

    ~any_option()
    {
        // delete our content when we're done
        delete mContent;
    }

    bool empty() const
    {
        return !mContent;
    }

    friend void swap(any_option& first, any_option& second)
    {
        std::swap(first.mContent, second.mContent);
    }

    // now we define the interface we'd like to support through erasure:

    // getting the data out if we know the type will be useful,
    // just like boost::any. (defined as friend free-function)
    template <typename T>
    friend T* any_option_cast(any_option*);

    // and the ability to query the type
    const std::type_info& type() const
    {
        return mContent->type(); // call actual function
    }

    // we also want to be able to call options_description::add_option(),
    // so we add a function that will do so (through a virtual call)
    void add_option(po::options_description desc, const char* name)
    {
        mContent->add_option(desc, name); // call actual function
    }

private:
    // done with the interface, now we define the non-template base class,
    // which has virtual functions where we need type-erased functionality
    class placeholder
    {
    public:
        virtual ~placeholder()
        {
            // allow deletion through base with virtual destructor
        }

        // the interface needed to support any_option operations:

        // need to be able to clone the stored value
        virtual placeholder* clone() const = 0;

        // need to be able to test the stored type, for safe casts
        virtual const std::type_info& type() const = 0;

        // and need to be able to perform add_option with type info
        virtual void add_option(po::options_description desc,
                                    const char* name) = 0;
    };

    // and the template derived class, which will support the interface
    template <typename T>
    class holder : public placeholder
    {
    public:
        holder(const T& value) :
        mValue(value)
        {}

        // implement the required interface:
        placeholder* clone() const
        {
            return new holder<T>(mValue);
        }

        const std::type_info& type() const
        {
            return typeid(mValue);
        }

        void add_option(po::options_description desc, const char* name)
        {
            desc.add_options()(name, po::value<T>(), "");
        }

        // finally, we have a direct value accessor
        T& value()
        {
            return mValue;
        }

    private:
        T mValue;

        // noncopyable, use cloning interface
        holder(const holder&);
        holder& operator=(const holder&);
    };

    // finally, we store a pointer to the base class
    placeholder* mContent;
};

class bad_any_option_cast :
    public std::bad_cast
{
public:
    const char* what() const throw()
    {
        return "bad_any_option_cast: failed conversion";
    }
};

template <typename T>
T* any_option_cast(any_option* anyOption)
{
    typedef any_option::holder<T> holder;

    return anyOption.type() == typeid(T) ? 
            &static_cast<holder*>(anyOption.mContent)->value() : 0; 
}

template <typename T>
const T* any_option_cast(const any_option* anyOption)
{
    // none of the operations in non-const any_option_cast
    // are mutating, so this is safe and simple (constness
    // is restored to the return value automatically)
    return any_option_cast<T>(const_cast<any_option*>(anyOption));
}

template <typename T>
T& any_option_cast(any_option& anyOption)
{
    T* result = any_option_cast(&anyOption);
    if (!result)
        throw bad_any_option_cast();

    return *result;
}

template <typename T>
const T& any_option_cast(const any_option& anyOption)
{
    return any_option_cast<T>(const_cast<any_option&>(anyOption));
}

// NOTE: My casting operator has slightly different use than
// that of boost::any. Namely, it automatically returns a reference
// to the stored value, so you don't need to (and cannot) specify it.
// If you liked the old way, feel free to peek into their source.

#include <boost/foreach.hpp>
#include <map>

int main()
{
    // (it's a good exercise to step through this with
    //  a debugger to see how it all comes together)
    typedef std::map<std::string, any_option> map_type;
    typedef map_type::value_type pair_type;

    map_type m;

    m.insert(std::make_pair("int", any_option(5)));
    m.insert(std::make_pair("double", any_option(3.14)));

    po::options_description desc;

    BOOST_FOREACH(pair_type& pair, m)
    {
        pair.second.add_option(desc, pair.first.c_str());
    }

    // etc.
}

Дайте мне знать, если что-то неясно. :)

person GManNickG    schedule 25.05.2011
comment
Большое спасибо. Я провел последние 3 часа, преобразовывая свой первоначальный класс Tuple в этот AnyOption, и это работает как мечта. Единственное добавление, которое я сделал, это дополнительная функция для дополнения add_option под названием from_option, чтобы покрыть случай variables_map::as<T> при возврате данных. Спасибо за это краткое, гибкое решение! - person Moo-Juice; 25.05.2011
comment
Красивое решение и красивое кодирование :) - person Arunmu; 17.12.2011

template<class T>
bool any_is(const boost::any& a)
{
    try
    {
        boost::any_cast<const T&>(a);
        return true;
    }
    catch(boost::bad_any_cast&)
    {
        return false;
    }
}

// ...

    po::options_description desc;
    std::for_each(m_Config.getTuples().begin(),
                  m_Config.getTuples().end(),
                  [&desc](const TuplePair& _pair)
    {
        if(any_is<int>(_pair.first))
        {
            desc.add_options() { _pair.first, po::value<int>, ""};
        }
        else if(any_is<std::string>(_pair.first))
        {
            desc.add_options() { _pair.first, po::value<std::string>, ""};
        }
        else
        {
            // ...
        }
    });

// ...

Если у вас больше нескольких типов, рассмотрите возможность использования списков типов.

person jhasse    schedule 25.05.2011
comment
Я считаю, что можно было бы использовать _pair.second.type() == typeid(int) и т.д. - person kloffy; 25.05.2011