I'm using this template to print different types of data to the console, however, if one argument is NULL
it generates an exception.
How I can add verification to avoid getting an exception?
I tried adding:
if (sizeof...(args) == 0)
return;
But it still reads NULL
values.
template <typename Arg, typename... Args>
void doPrint(Arg&& arg, Args&&... args)
{
if (sizeof...(args) == 0)
return;
std::wostringstream out;
out << std::forward<Arg>(arg);
using expander = int[];
(void)expander {
0, (void(out << std::left << std::setw(20) << std::forward<Args>(args)), 0)...
};
OutputDebugString(out.str().c_str());
std::cout << out.str().c_str() << std::endl;
}
How to reproduce the exception:
BSTR name = NULL;
doPrint("name: ", name);
CodePudding user response:
Issue is that operator <<
taking BSTR
expects non-null pointer.
BSTR name = nullptr;
out << name; // Wrong expect non-null pointer
You might create overload for custom display and handling special case:
template <typename T>
std::wostream& doPrintImpl(std::wostream& out, const T& arg)
{
return out << arg;
}
std::wostream& doPrintImpl(std::wostream& out, const BSTR arg)
{
return out << (arg == nullptr ? L"NULL" : arg);
}
template <typename... Args>
void doPrint(const Args&... args)
{
std::wostringstream out;
auto print = [&](const auto& arg){
doPrintImpl(out, arg);
out << std::left << std::setw(20);
};
using expander = int[];
(void)expander { 0, (print(args), 0)... }; // equivalent of C 17 (print(args), ...);
OutputDebugString(out.str().c_str());
std::cout << out.str().c_str() << std::endl;
}
CodePudding user response:
First:
if (sizeof...(args) == 0)
This only check how many arguments you have. It doesn't do anything about what they actually are.
Instead you need to actually check if each arguments are nullptr
. Naturally, you want something like:
args != nullptr
? out << std::left << std::setw(20) << std::forward<Args>(args)
: out
This would work if all args
are meant to be pointer types. However, it will fail if you pass in anything else, since operator!=
isn't defined between value types and std::nullptr_t
.
Instead, you could write a set of helper functions isNullptr
, that will do either return false if you pass in a value type, or check if they are nullptr
if you pass in a pointer type:
template <typename T>
bool isNullptr(T* ptr)
{
return !ptr;
}
template <typename T>
bool isNullptr(T)
{
return false;
}
Now, you can change your previous statement to:
!isNullptr(args)
? out << std::left << std::setw(20) << std::forward<Args>(args)
: out
And you can put it in your expander:
(void)expander {((!isNullptr(args) ? out << std::left << std::setw(20) << std::forward<Args>(args) : out), 0)...};
Or for c 17 or later:
((!isNullptr(args) ? out << std::left << std::setw(20) << std::forward<Args>(args) : out), ...);