Specialize function if argument has member variable

c++ template specialization member function
function template specialization
c++ partial template specialization function
c++ template class member function partial specialization
partial template specialization c++
non-class, non-variable partial specialization
template parameters not deducible in partial specialization
template specialization vs overloading

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.

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.

explicit (full) template specialization - cppreference , variable templates(C++14) class template argument deduction(C++17) a class or class template; member function template of a class or class template An explicit specialization of a function template is inline only if it is  Default function arguments cannot be specified in explicit specializations of function templates, member function templates, and member functions of class templates when the class is implicitly instantiated. An explicit specialization cannot be a friend declaration . If

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

partial template specialization, Allows customizing class and variable (since C++14) templates for a given 3) If any argument is a pack expansion, it must be the last argument in the list as the first partial specialization and has just one function parameter, whose If a primary template is a member of another class template, its partial  Member variables are effectively a global state from the point of view, what's more, a mutable global state if said member changes during the method's execution. By replacing member variable references with method parameters, we can effectively make a function pure.

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

explicit (full) template specialization, Allows customizing the template code for a given set of template arguments. class template; (since C++14)variable template · member function of a class An explicit specialization of a function template is inline only if it is declared with the of an explicitly specialized member class template, which is specialized as a  That is, each class's member functions have free access to the other's private members. FALSE True/False: By default, when an object is assigned to another, each member of one object is copied to its counterpart in the other object.

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

Full class template specialization and full member specialization , This class template contains three member: a member function, a static data A real type is made of a template name plus its arguments, explicit or deduced If we create a variable of type, say, AClass<double>, the compiler  A non-template member function and a template member function with the same name may be declared. In case of conflict (when some template specialization matches the non-template function signature exactly), use of that name and type refers to the non-template member unless an explicit template argument list is supplied.

13.5, This is extremely simple: simply define the specialized function (if the function is a member function, do so outside of the class definition), replacing the the compiler that this is a template function, but that there are no template parameters Now when we allocate a variable of type Storage<char*>, this  That is, each class's member functions have free access to the other's private members. False True/False: By default, when an object is assigned to another, each member of one object is copied to its counterpart in the other object.

Templates, C++ FAQ, Can you provide an example of template specialization that doesn't use foo and bar ? But most of the code in my template function is the same; is there some way to get the Why can't I define constraints for my template parameters? Why am I getting errors when my template-derived-class uses a member it inherits from  Argument is a see also of variable. As nouns the difference between argument and variable is that argument is a fact or statement used to support a proposition; a reason while variable is something that is. As a adjective variable is able to vary.

[temp.spec], A specialization is a class, variable, function, or class member that is either instantiated For a given template and a given set of template-arguments,. (5.1). Another object of the same class is passed as a reference in the argument which is then used to initialize the member variables of the concerned object. It is usually called when a function returns an object, or an object is being passed as an argument to a function. It has the following syntax of defining it.

Comments
  • I vote to reopen this question. Even though specializing a template based on whether a specific member exist is a way to answer OP's question, it is not the only one.
  • It is almost exactly the same with functions. One just needs to make a helper template class.
  • I think it's fair to ask if it's possible to do without the helper class. It may be that it's not possible, but simply marking this as duplicate doesn't answer that question.
  • I really like that priority/choice trick. It looks more of a hack, but for C++03 compilers I cannot imagine simplest.
  • @YSC Well, it's a way to exploit overloading, I wouldn't say a hack. After all, this is what our language offers and there are cases where this trick is useful even if you're using C++17. The fact is that with C++17 you have several solutions from which to choose, with C++11 only partially implemented you have much less freedom. :-)
  • You can use the typeof gcc extension, and use 0 instead of nullptr to make it work together with this old compiler. godbolt.org/g/DLEhrP
  • Thanks for pointing this out, @Rudi. Actually, code I've posted works with 4.6 (when you add -std=c++0x). Updated my answer accordingly. It would seem that this solution would work with C++03 compiler with typeof extension (or equivalent, I think that MSVC has had decltype before C++11).
  • Surprisingly, this isn't actually working with my gcc 4.6.3 cross-compiler. It compiles just fine, but it calls the variadic/non-specific overload regardless of whether the details field exists! Seems like that's possibly just a bug in 4.6.3 since it works just fine in later versions
  • Update: if I cast NULL to be a pointer to std::string, then it works as intended...
  • @elvis.dukaj yeah, at the time this answer was written, it was not a requirement yet. I leave it anyway because it could help others.