Автоматическое выведение типов шаблонов, путающие указатели и ссылки
Пытаясь отладить некоторый код, я создал класс, который выводит значения сложной иерархии объектов в текстовый файл, чтобы я мог сравнить случай, когда он работает, со случаем, когда это не так. Я реализовал класс следующим образом (приведен простой пример):
#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 {}