Как напечатать объект неизвестного типа - PullRequest
4 голосов
/ 22 июля 2010

У меня есть шаблонизированный контейнерный класс в C ++, который похож на std :: map (в основном это потокобезопасная оболочка для std :: map). Я хотел бы написать функцию-член, которая выводит информацию о записях на карте. Очевидно, однако, я не знаю тип объектов на карте или их ключи. Цель состоит в том, чтобы иметь возможность обрабатывать базовые типы (целые числа, строки), а также некоторые конкретные типы классов, которые меня особенно интересуют. Для любого другого класса я хотел бы, по крайней мере, скомпилировать и, предпочтительно, сделать что-то несколько умное например, распечатать адрес объекта. Мой подход до сих пор похож на следующий (обратите внимание, я на самом деле не скомпилировал это или что-нибудь ...):

template<typename Index, typename Entry>
class ThreadSafeMap
{
    std::map<Index, Entry> storageMap;
    ...
    dumpKeys()
    {
        for(std::map<Index, Entry>::iterator it = storageMap.begin();
            it != storageMap.end();
            ++it)
        {
            std::cout << it->first << " => " << it->second << endl;
        }
    }
    ...
}

Это работает для основных типов. Я также могу написать собственные функции вставки потока для обработки определенных классов, которые меня интересуют. Однако я не могу найти хороший способ обработать случай по умолчанию, где Index и / или Entry - необработанный произвольный тип класса , Есть предложения?

Ответы [ 2 ]

14 голосов
/ 22 июля 2010

Вы можете предоставить шаблонный оператор << для отлова случаев, когда пользовательский оператор вывода не определен, так как предпочтение отдается любым более специализированным версиям.Например:

#include <iostream>

namespace detail
{
    template<typename T, typename CharT, typename Traits>
    std::basic_ostream<CharT, Traits> &
    operator<<(std::basic_ostream<CharT, Traits> &os, const T &)
    {
        const char s[] = "<unknown-type>";
        os.write(s, sizeof(s));
        return os;
    }
}

struct Foo {};

int main()
{
    using namespace detail;
    std::cout << 2 << "\n" << Foo() << std::endl;
    return 0;
}

выведет:

2
<unknown-type>

detail namespace, чтобы этот оператор вывода «по умолчанию» не вмешивался в код в местах, отличных отнеобходимо.То есть вы должны использовать его (как в using namespace detail) только в своем методе dumpKeys().

11 голосов
/ 23 июля 2010

У меня изначально был только более канонический способ использования Ответ Стаффана . Тем не менее, jpalecek правильно указал на большой недостаток с подходом.

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

Что нужно сделать, это сделать этот оператор вставки шаблона преобразованием (при сохранении его общности), чтобы можно было рассмотреть другие преобразования. Если другие не найдены, , то будет преобразовано в универсальный оператор вставки.

Код утилиты таков:

#include <iosfwd>
#include <memory>

namespace outputter_any_detail
{
    // your generic output function
    template <typename T>
    std::ostream& output_generic(std::ostream& pStream, const T& pX)
    {
        // note: safe from recursion. if you accidentally try 
        // to output pX again, you'll get a compile error
        return pStream << "unknown type at address: " << &pX;
    }

    // any type can be converted to this type,
    // but all other conversions will be 
    // preferred before this one
    class any
    {
    public:
        // stores a type for later output
        template <typename T>
        any(const T& pX) :
        mPtr(new any_holder<T>(pX))
        {}

        // output the stored type generically
        std::ostream& output(std::ostream& pStream) const
        {
            return mPtr->output(pStream);
        }

    private:
        // hold any type
        class any_holder_base
        {
        public:
            virtual std::ostream& output(std::ostream& pStream) const = 0;
            virtual ~any_holder_base(void) {}
        };

        template <typename T>
        class any_holder : public any_holder_base
        {
        public:
            any_holder(const T& pX) :
            mX(pX)
            {}

            std::ostream& output(std::ostream& pStream) const
            {
                return output_generic(pStream, mX);
            }

        private:
            const T& mX;
            any_holder& operator=(const any_holder&);
        };

        std::auto_ptr<any_holder_base> mPtr;
        any& operator=(const any&);
    };

    // hidden so the generic output function
    // cannot accidentally call this fall-back
    // function (leading to infinite recursion)
    namespace detail
    {
        // output a type converted to any. this being a conversion allows
        // other conversions to partake in overload resolution
        std::ostream& operator<<(std::ostream& pStream, const any& pAny)
        {
            return pAny.output(pStream);
        }
    }

    // a transfer class, to allow
    // a unique insertion operator
    template <typename T>
    class outputter_any
    {
    public:
        outputter_any(const T& pX) :
          mX(pX)
          {}

          const T& get(void) const
          {
              return mX;
          }

    private:
        const T& mX;
        outputter_any& operator=(const outputter_any&);
    };

    // this is how outputter_any's get outputted,
    // found outside the detail namespace by ADL
    template <typename T>
    std::ostream& operator<<(std::ostream& pStream, const outputter_any<T>& pX)
    {
        // bring in the fall-back insertion operator
        using namespace detail;

        // either a specifically defined operator,
        // or the generic one via a conversion to any
        return pStream << pX.get();
    }
}

// construct an outputter_any
template <typename T>
outputter_any_detail::outputter_any<T> output_any(const T& pX)
{
    return outputter_any_detail::outputter_any<T>(pX);
}

Вставьте его в заголовок, например "output_any.hpp". И вы используете это так:

#include <iostream>
#include "output_any.hpp"    

struct foo {}; 
struct A {}; 
struct B : A {};

std::ostream& operator<<(std::ostream& o, const A&)
{
    return o << "A";
}

int main(void)
{
    foo f;
    int i = 5;
    B b;

    /*

    Expected output:
    unknown type at address: [address]
    5
    [address] 
    A
    */                                       // output via...  
    std::cout << output_any(f) << std::endl; // generic
    std::cout << output_any(i) << std::endl; // int
    std::cout << output_any(&i) << std::endl;// void*
    std::cout << output_any(b) << std::endl; // const A&
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...