自动模板类型推导混淆指针和引用
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;
}
这是必需的,因为当实例化模板时,将提供 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 {}
在尝试调试一些代码时,我创建了一个 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;
}
这是必需的,因为当实例化模板时,将提供 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 {}