Search code examples
c++metaprogrammingsfinaeenable-if

C++ SFINAE - Resolution priority between std::is_arithmetic and std::is_same


I'm using SFINAE to build a rudimentary serialization library.

Let's say I have a class that implements a generic process method, that reads in any type (allowing for user-extension) and serializes it. I'm using std::enable_if to specialize this method to different template argument types. Here's an example:

class OutputSerializer
{
public:
    OutputSerializer(std::ostream& str) : stream(str) {}

private:
    template<typename T>
    typename std::enable_if<std::is_arithmetic<T>::value, void>::type
    process(T&& arg) {
        stream.write(&arg, sizeof(arg));
    }

//More specializations here

    std::ostream& stream;
};

I want to optimize this method for booleans, by making the output stream store 1 byte, instead of sizeof(bool) bytes. Similarly to how I did before, I add a new template specialization:

class OutputSerializer
{
public:
    OutputSerializer(std::ostream& str) : stream(str) {}

private:
    template<typename T>
    typename std::enable_if<std::is_arithmetic<T>::value, void>::type
    process(T&& arg) {
        stream.write(&arg, sizeof(arg));
    }

    template<typename T>
    typename std::enable_if<std::is_same<T, bool>::value, void>::type
    process(T&& arg) {
        stream.write(&arg, 1);
    }

    std::ostream& stream;
};

Now, a question arises. std::is_arithmetic<bool> is also supposed to return true. So which of the two specializations will be given priority during name resolution?

Keep in mind, this is only a theoretical question. I know of more ways to make sure this method does what I want it to, such as specializing the template itself for bool, like this:

template<>
void process<bool>(bool&& arg) {
    stream.write(&arg, 1);
}

or by adding an extra check in the more generic case, like this:

template<typename T>
typename std::enable_if<
    std::is_arithmetic<T>::value
    && !std::is_same<T, bool>::value, void>::type
process(T&& arg) {
    stream.write(&arg, sizeof(arg));
}

Or at least, I'm pretty sure these would work. Feel free to call me out on it if I'm wrong. Thank you!


Solution

  • Neither has any sort of priority. Both templates will result in the function void process(bool) and the call will be ambiguous.

    Example

    Adding an explicit specialization for bool won't work either, since there's no clear base template to specialize. You need to add more conditions to the std::is_arithmetic version of the template, or better just provide a non-template overload for bool. Functions do have priority over function templates, so a non-template version will be chosen by overload resolution.

    Example