Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specialize function if argument has member variable

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.

like image 618
Alec Avatar asked Oct 17 '25 07:10

Alec


2 Answers

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

like image 138
joe_chip Avatar answered Oct 19 '25 22:10

joe_chip


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.

like image 44
skypjack Avatar answered Oct 19 '25 22:10

skypjack



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!