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

时间:2017-09-22 09:39:49

标签: c++ templates template-deduction

在尝试调试某些代码时,我创建了一个类来将一个复杂的对象层次结构的值转储到一个文本文件中,这样我就可以将它的工作情况与不工作的情况进行比较。我实现了这样的类(简化为一个例子):

#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 个答案:

答案 0 :(得分:0)

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 *,因为在这种情况下所需的转换序列将仅包括被视为身份转换的引用绑定{{1带const _T* pBar的变体将涉及复制初始化。

答案 1 :(得分:0)

以下是一些修改和注释,可能有助于此日志记录类的增长并变得更加成熟。

我试图:

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