Автоматическое выведение типов шаблонов, путающие указатели и ссылки

Пытаясь отладить некоторый код, я создал класс, который выводит значения сложной иерархии объектов в текстовый файл, чтобы я мог сравнить случай, когда он работает, со случаем, когда это не так. Я реализовал класс следующим образом (приведен простой пример):

#include <iostream>

class someOtherClass
{
public:
    someOtherClass()
        : a(0)
        , b(1.0f)
        , c(2.0)
    {}
    int a;
    float b;
    double c;
};

class logger
{
public:
    // Specific case for handling a complex object
    logger& operator << ( const someOtherClass& rObject )
    {
        std::cout << rObject.a << std::endl;
        std::cout << rObject.b << std::endl;
        std::cout << rObject.c << std::endl;
        return *this;
    }

    // [other class specific implementations]

    // Template for handling pointers which might be null
    template< typename _T >
    logger& operator << ( const _T* pBar )
    {
        if ( pBar )
        {
            std::cout << "Pointer handled:" << std::endl;
            return *this << *pBar;
        }
        else
            std::cout << "null" << std::endl;
        return  *this;
    }

    // Template for handling simple types.
    template< typename _T >
    logger& operator << ( const _T& rBar )
    {
        std::cout << "Reference: " << rBar << std::endl;
        return *this;
    }
};

int main(int argc, char* argv[])
{
    logger l;
    someOtherClass soc;
    someOtherClass* pSoc = &soc;
    l << soc;
    l << pSoc;
    pSoc = nullptr;
    l << pSoc;
    return 0;
}

Я ожидал получить следующий вывод:

0
1
2
Pointer handled:
0
1
2
null

Но то, что я на самом деле получаю, это:

0
1
2
Reference: 010AF7E4
Reference: 00000000

Похоже, что автоматический вывод типа выбирает эталонную реализацию и устанавливает тип в someOtherClass* вместо выбора реализации указателя. Я использую Visual Studio 2012.

2 ответа

Решение

В logger& operator << ( const _T& rBar ) тип T может быть указателем типа, поэтому для правильной работы этого шаблона требуется некоторое ограничение:

template< typename _T , typename = typename ::std::enable_if_t<!std::is_pointer<_T>::value> >
logger& operator << ( const _T& rBar )
{
    std::cout << "Reference: " << rBar << std::endl;
    return *this;
}

Онлайн компилятор

Это необходимо, потому что когда создаются шаблоны const _T & pBar с _T = someOtherClass * вариант будет предложен, так как последовательность преобразования, требуемая в этом случае, будет включать только эталонную привязку, которая считается преобразованием идентификатора, в то время как const _T* pBar вариант с _T = someOtherClass будет включать в себя копию инициализации.

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

Я пытался:

а) решить начальную проблему неправильного вывода типа.

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

c) предоставить механизм, позволяющий легко разрешать ведение журнала любого типа, даже если он предоставляется сторонней библиотекой.

#include <iostream>

// I've put the logger and its helpers into a namespace. This will keep code tidy and help with
// ADL.
namespace logging 
{
    // define a general function which writes a value to a stream in "log format".
    // you can specialise this for specific types in std:: if you wish here
    template<class T> 
    void to_log(std::ostream& os, T const& value)
    {
        os << value;
    }

    // define a general function objects for writing a log-representation of tyoe T.
    // There are 2 ways to customise this.
    // a) provide a free function called to_log in the same namespace as your classes (preferred)
    // b) specialise this class.
    template<class T>
    struct log_operation
    {
        void operator()(std::ostream& os, T const& value) const
        {
            to_log(os, value);
        }
    };

    // specialise for any pointer
    template<class T>
    struct log_operation<T*>
    {
        void operator()(std::ostream& os, T* ptr) const
        {
            if (!ptr)
                os << "null";
            else
            {
                os << "->";
                auto op = log_operation<std::decay_t<T>>();
                op(os, *ptr);
            }
        }
    };

    // the logger is now written in terms of log_operation()
    // it knows nothing of your application's types
    class logger
    {
    public:

        // Template for handling any type.
        // not that this will also catch pointers.
        // we will disambiguate in the log_operation
        template< typename T >
        logger& operator << ( const T& rBar )
        {
            auto op = log_operation<std::decay_t<T>>();
            op(std::cout, rBar);
            std::cout << std::endl;
            return *this;
        }
    };
}

class someOtherClass
{
public:
    someOtherClass()
        : a(0)
        , b(1.0f)
        , c(2.0)
    {}
    int a;
    float b;
    double c;
};

// someOtherClass's maintainer provides a to_log function
void to_log(std::ostream& os, someOtherClass const& c)
{
    os << "someOtherClass { " << c.a << ", " << c.b << ", " << c.c << " }";
}

namespace third_party
{
    // the is in a 3rd party library. There is no to_log function and we can't write one which will be found with
    // ADL...
    struct classWhichKnowsNothingOfLogs {};
}

/// ..so we'll specialise in the logging namespace

namespace logging
{
    template<>
    struct log_operation<::third_party::classWhichKnowsNothingOfLogs>
    {
        void operator()(std::ostream& os, ::third_party::classWhichKnowsNothingOfLogs const& value) const
        {
            os << "classWhichKnowsNothingOfLogs {}";
        }
    };
}


int main(int argc, char* argv[])
{
    logging::logger l;
    someOtherClass soc;
    someOtherClass* pSoc = &soc;
    l << soc;
    l << pSoc;
    pSoc = nullptr;
    l << pSoc;

    l << third_party::classWhichKnowsNothingOfLogs();
    return 0;
}

ожидаемый результат:

someOtherClass { 0, 1, 2 }
->someOtherClass { 0, 1, 2 }
null
classWhichKnowsNothingOfLogs {}
Другие вопросы по тегам