自动模板类型推导混淆指针和引用

Automatic template type deduction confusing pointers and references

在尝试调试一些代码时,我创建了一个 class 来将复杂的对象层次结构的值转储到文本文件中,这样我就可以比较它有效的情况和无效的情况't。我像这样实现了 class(简化为一个简单的例子):

#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.

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;
}

Online compiler

这是必需的,因为当实例化模板时,将提供 const _T & pBar_T = someOtherClass * 变体,因为在这种情况下所需的转换序列将仅包括一个引用绑定,该引用绑定被视为身份转换,而 const _T* pBar 带有 _T = someOtherClass 的变体将涉及复制初始化。

这里有一些修改和注释,随着此日志 class 的发展和成熟,它们可能会有所帮助。

我尝试过:

a) 解决初始类型推导不正确的问题

b) 将记录器与记录的内容分离(否则你的记录器必须了解整个应用程序和所有库)。

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 {}