I have a function for error reporting that is templated because it can report errors for many different message classes:
template <typename MSG>
void reportErr(const MSG& msg)
{
std::cout << "ERROR: " << msg.error << std::endl;
}
However, some types of message have more detailed error that can be reported or other specialized error reporting, e.g.
template<>
void reportErr(const SpecificMsg& msg)
{
std::cout << "ERROR: " << msg.error;
std::cout << ", details: " << msg.details << std::endl;
}
Since there are many types like SpecificMsg
, I'd rather not create an individual template specialization for each type. Is it possible to create a generic specialization/partial specialization for any type that has a .details
member variable?
If possible, I'd like a way to do this generally (so one specialization if it has .details
, a different one if it has .other_info
, etc).
Edit: This is explicitly asking about functions. I've seen code that does similar things to specialize template classes, but I've never encountered something that does what I want for non-member functions. I suspect it isn't hard to convert the approach used for classes to work for functions, but I haven't been able to figure out how to do it.
Edit 2: my version of gcc (4.6.3) appears not to support the full C++11 standard, so the void_t
option mentioned in the "duplicate" question doesn't work for me. My compiler complains "expected nested-name-specifier before 'type'" etc and won't even let me define void_t. As such, I've removed the C++11 tag from my question.
I'd use SFINAE for this. First, let's define two functions, which return error a string for a message:
namespace detail
{
// for messages with "details" member:
template<typename MsgType>
std::string makeMsgString(const MsgType& msg, decltype(MsgType::details)*)
{
return "Error: " + msg.error + ", details: " + msg.details;
}
// for messages without "details" member:
template<typename MsgType>
std::string makeMsgString(const MsgType& msg, ...)
{
return "Error: " + msg.error + ", no details";
}
}
Now, these functions can be used like that:
struct NonSpecificMsg { std::string error; };
struct SpecificMsg { std::string error, details; };
template<typename MsgType>
void reportErr(const MsgType& msg)
{
std::cout << detail::makeMsgString(msg, nullptr) << "\n";
}
int main()
{
reportErr(NonSpecificMsg { "some error" }); // 1
reportErr(SpecificMsg { "some other error", "some details" }); // 2
return 0;
}
What happens here?
Call 1): NonSpecificMsg
does not have a details
member, therefore the first overload does not exist. Since MsgType::details
does not exist, decltype(MsgType::details)*
is not a valid type. SFINAE causes this definition to be ignored instead of throwing an error during compilation.
There's only overload 2), which does not access details
member.
Call 2): SpecificMsg
has details
, so both overloads are considered by a compiler. However, variadic function overloads (the second one) always have lower priority than any other matching overload, so the first one is chosen.
Edit: this is a C++11 solution. Unfortunately, decltype
was introduced in GCC 4.8.
Edit 2: It turns out that decltype
can be used with GCC 4.6 (it was introduced with version 4.3). Version 4.8.1 changed its semantics, but in OP's case, previous versions will work - see GCC's C++ status page
If possible, I'd like a way to do this generally (so one specialization if it has .details, a different one if it has .other_info, etc).
If I got your expectation, you can use the choice
-trick combined with decltype
as it happens in the following example:
#include <iostream>
template<int N>
struct choice: choice<N-1> {};
template<>
struct choice<0> {};
struct Foo { int error; };
struct Bar { int error; int details; };
struct Quux { int error; char other_info; };
template<typename MSG>
auto reportErr(choice<2>, const MSG& msg) -> decltype(msg.details, void()) {
std::cout << "ERROR: " << msg.error;
std::cout << ", details: " << msg.details << std::endl;
}
template<typename MSG>
auto reportErr(choice<1>, const MSG& msg) -> decltype(msg.other_info, void()) {
std::cout << "ERROR: " << msg.error;
std::cout << ", other_info: " << msg.other_info << std::endl;
}
template <typename MSG>
void reportErr(choice<0>, const MSG& msg) {
std::cout << "ERROR: " << msg.error << std::endl;
}
template <typename MSG>
void reportErr(const MSG &msg) {
reportErr(choice<100>{}, msg);
}
int main() {
reportErr(Foo{0});
reportErr(Bar{0, 42});
reportErr(Quux{0, 'c'});
}
See it up and running on wandbox
(using GCC 4.5.4 actually, the version you mentioned isn't available). It exploits overloading resolution to pick up a working version of the function according to the type of the message and discards all what's in between. You can add more specializations (let's call them so, even though they are not properly specializations after all) and sort them according to your preferences by adjusting the choice
parameter as needed (the higher its value, the higher the priority of the specialization).
Something similar can also be done by combining the choice
-trick with sizeof
in a SFINAE'd based solution similar to what I shown above.
In particular, here is a working example:
#include <iostream>
template<int N>
struct choice: choice<N-1> {};
template<>
struct choice<0> {};
struct Foo { int error; };
struct Bar { int error; int details; };
struct Quux { int error; char other_info; };
template<typename MSG, std::size_t = sizeof(MSG::details)>
void reportErr(choice<2>, const MSG& msg) {
std::cout << "ERROR: " << msg.error;
std::cout << ", details: " << msg.details << std::endl;
}
template<typename MSG, std::size_t = sizeof(MSG::other_info)>
void reportErr(choice<1>, const MSG& msg) {
std::cout << "ERROR: " << msg.error;
std::cout << ", other_info: " << msg.other_info << std::endl;
}
template <typename MSG>
void reportErr(choice<0>, const MSG& msg) {
std::cout << "ERROR: " << msg.error << std::endl;
}
template <typename MSG>
void reportErr(const MSG &msg) {
reportErr(choice<100>{}, msg);
}
int main() {
reportErr(Foo{0});
reportErr(Bar{0, 42});
reportErr(Quux{0, 'c'});
}
See it up and running on wandbox
. The advantage is that this solution doesn't suffer from the annoying warning you receive with the previous one.
I tested it with an older compiler than what you asked (GCC 4.5.4), so I'm pretty confident they both work also with GCC 4.6.x.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With