Specialize function if argument has member variable

烈酒焚心 提交于 2019-12-12 12:32:46

问题


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.


回答1:


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.




回答2:


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




回答3:


Note: This is a C++17 answer written before OP specified their gcc/c++ version. I let it there to hopefully help others.

You can tag your message types and test those tag at compile time:

#include <iostream>
#include <type_traits>
#include <string>

struct HasErrorMember { std::string error = "error"; };
struct HasDetailsMember { std::string details = "details"; };

template<class MSG>
void reportErr(const MSG& msg)
{
    if constexpr (std::is_base_of_v<HasErrorMember, MSG>)   std::cout << "ERROR: " << msg.error;
    if constexpr (std::is_base_of_v<HasDetailsMember, MSG>) std::cout << ", details: " << msg.details;
    std::cout << "\n";
}

struct MsgSimple : HasErrorMember
{};

struct MsgDetails : HasErrorMember, HasDetailsMember
{};

int main()
{
    MsgSimple  ms;
    MsgDetails md;
    std::cout << "error only:\n";
    reportErr(ms);
    std::cout << "error + details:\n";
    reportErr(md);
}

Accordingly to your needs, those tag can embed the members themselves or can be empty, putting the responsibility to ensure member<->tag consistency to the developer.

live demo




回答4:


With only C++03, traits is more verbose than with C++11 (as std::is_detected), you might do something like:

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)               \
    template <typename U>                                                   \
    class traitsName                                                        \
    {                                                                       \
    private:                                                                \
        template<typename T, T> struct helper;                              \
        template<typename T>                                                \
        static char check(helper<signature, funcName>*);                    \
        template<typename T> static int check(...);                         \
    public:                                                                 \
        static                                                              \
        const bool value = sizeof(check<U>(0)) == sizeof(char);             \
    }

then

// Would be in std in C++11
template <bool, typename T = void> struct enable_if
{
    typedef T type;
};

template <typename T> struct enable_if<false, T>
{
};

and then

DEFINE_HAS_SIGNATURE(has_details, &T::details, std::string (T::*));

template <typename MSG>
typename enable_if<!has_details<MSG>>::type
reportErr(const MSG& msg)
{
    std::cout << "ERROR: " << msg.error << std::endl;
}

template <typename MSG>
typename enable_if<has_details<MSG>>::type
void reportErr(const MSG& msg)
{
    std::cout << "ERROR: " << msg.error;
    std::cout << ", details: " << msg.details << std::endl;
}

Demo



来源:https://stackoverflow.com/questions/50295630/specialize-function-if-argument-has-member-variable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!