Search code examples
c++11templatesoverloadingfallbacktype-deduction

Specialize C++11 template based on type traits while allowing fallback for other types


I am currently working on serializing several data structures to JSON.

The existing data structures already provide a text-based output format that I want to use as the default when the functions that convert a specific type to JSON not yet exists.

I currently have the fallback and several specific specializations done. As the JSON-library I use is already able to consume primintives like int, float etc. without any extra work, I want these types have their own (common) specialized template function.

My current main problem is that I'm not able to get to_json(1) to call the function for fundamentals/primitives, but if I specify the type manually (to_json<int>) the correct function is called. Is there any way to fix this? What type does the compiler deduce in the first line?

I am limited to C++11.

The following summarizes my basic setup


#include <iostream>
#include <type_traits>

// Should rely on fallback
struct S {
};

// Has specialization
struct R {
};


// Fallback. Should catch everything not caught by the specializations
template <typename T>
void to_json(const T& x);


// Specialization for R
template<>
void to_json(const R& x)
{
    std::cout << "For R\n";
}

// Does not work
// Specialization for all fundamentals (e.g. int)
template<typename T>
void to_json(const typename std::enable_if<std::is_fundamental<T>::value, T>::type& x)
{
    std::cout << "For fundamentals\n";
}

template <typename T>
void to_json(const T& x)
{
    std::cout << "General\n";
}

int main()
{
    to_json(1);      // General. (WRONG, expected fundamentals)
    to_json<int>(1); // fundamentals (CORRECT)
    to_json(R{});    // R (CORRECT)
    to_json(S{});    // General (CORRECT)

    return 0;
}

Output:

General
For fundamentals
For R
General

Solution

  • Your forward declare template function will capture all types. It gives trouble when initialise with concrete type.

    //// Fallback. Should catch everything not caught by the specializations
    //template <typename T>
    //void to_json(const T& x);
    

    Below code should work fine:

    // Should rely on fallback
    struct S {
    };
    
    // Has specialization
    struct R {
    };
    
    // note: also use enable_if_t to exclude fundamental type
    //       otherwise compiler is still confused by the redefinition
    template <typename T,
            std::enable_if_t<!std::is_fundamental<T>::value, bool> = true>
    void to_json(const T& x)
    {
        std::cout << "General\n";
    }
        
    // Specialization for R
    template<>
    void to_json(const R& x)
    {
        std::cout << "For R\n";
    }
    
    // Specialization for all fundamentals (e.g. int)
    template<typename T,
             std::enable_if_t<std::is_fundamental<T>::value, bool> = true>
    void to_json(const T& x)
    {
        std::cout << "For fundamentals\n";
    }
    
    int main()
    {
        to_json(1);      // General. (WRONG, expected fundamentals)
        to_json<int>(1); // fundamentals (CORRECT)
        to_json(R{});    // R (CORRECT)
        to_json(S{});    // General (CORRECT)
    
        return 0;
    }