Search code examples
c++templatessfinaepartial-specialization

Using SFINAE partial specialization without touching the primary template


I'm trying to specialize a struct template for multiple types at once using SFINAE. I know that something like the following works:

#include <iostream>

template <typename T, typename Enable = void>
struct S {
    void operator()() {
        std::cout << "Instantiated generic case" << std::endl;
    }
};

template<typename T>
using enabled_type = typename std::enable_if<
                         std::is_same<T, int>::value ||
                         std::is_same<T, float>::value
                     >::type;

template <typename T>
struct S<T, enabled_type<T>> {
    void operator()() {
        std::cout << "Instantiated int/float case" << std::endl;
    }
};

int main() {
    S<float>()();
    return 0;
}

My problem is that I can't modify the primary template of the S struct to add typename Enable = void, as it's part of an external header-only library. So the primary template will have to look like this:

template <typename T>
struct S {
    void operator()() {
        std::cout << "Instantiated generic case" << std::endl;
    }
};

Is there a way that I could still use SFINAE to specialize this template?

Edit: Note that the S struct is used by code in the external library, so I will have to actually specialize S and can't subclass it. Also, the real code I'm working on is much more complicated and would benefit much more from SFINAE than this simple example (I have multiple template parameters that need to be specialized for all combinations of a number of types).


Solution

  • I have bad news for you: what you want is impossible. If the primary template does not have an extra template parameter that defaults to void for the purpose of doing enable_if, there's just no way to do this in the most general sense. The closest you can come is to specialize the struct for a template class itself, in other words:

    template <class T>
    struct foo {};
    
    template <class T>
    struct S<foo<T>> {};
    

    This will work. But obviously this does not yield the same flexibility as specializing something iff it matches a trait.

    Your problem is actually exactly equivalent to the problem of trying to specialize std::hash for any types satisfying a trait. Like in your problem, the primary class template definition cannot be changed as it's in library code, and the library code actually uses specializations of hash automatically internally in certain situations, so one cannot really do with defining a new template class.

    You can see a similar question here: Specializing std::hash to derived classes. Inheriting from a class is a good example of something that can be expressed as a trait, but not as a templated class. Some pretty knowledgeable C++ folk had eyes on that question, and nobody provided a better answer than the OP's solution of writing a macro to stamp out specialization automatically. I think that that will be the best solution for you too, unfortunately.

    In retrospect, hash perhaps should have been declared with a second template parameter that defaulted to void to support this use case with minimal overhead in other cases. I could have sworn I even saw discussion about this somewhere but I'm not able to track it down. In the case of your library code, you might try to file an issue to have them change that. It does not seem to be a breaking change, that is:

    template <class T, class = void>
    struct S {};
    
    template <>
    struct S<double> {};
    

    seems to be valid.