Disable default template and only use specializati

2019-07-20 03:16发布

问题:

Consider the following system:

template<typename T>
    struct wrapper
    {
        operator T * () { return nullptr; }
    };

template<typename Ret, typename T>
    Ret func(T);

template<>
    int func(float * in)
    {
        std::cout << "long";
    }

template<>
    long func(float * in)
    {
        std::cout << "int";
    }

The purpose of the wrapper is to allow it to decay to the type it is templated to (it is a wrapper around a buffer of the type). Moreover, i have a set of functions that are templated specializations of a template. This is to circumvent the usual error when overloading based on only the return type.

This doesn't work though, as noted here:

// the following should work, but doesn't because it's instantiating 
// the func<ret, wrapper<float>> which doesn't exist resulting in a linker error
// instead of selecting the int func(float *) overload
wrapper<float> w;
func<int>(w);

Conversely, i would like this to generate a compile-time error (but again, it's generating a link-time error):

// the following should generate a compile-time error
// since no explicit overload for int func(int *) exists
wrapper<int> w2;
func<int>(w2);

So ideally, i would like to disable the original template (maybe through sfinae if this is possible?) such that the overload resolution only considers the explicit specializations, and generates a compile-time error if no match is found. Can this be done?

A portable solution between clang and msvc is a must, but I'm using the newest versions of both.

回答1:

If you do

template<typename Ret> Ret func(float*);

it works as expected: Live example



回答2:

While Jarod's answer solved one of the problems, i still needed a way to overload the function arguments (which in that case would generate 'no matching template' errors) - i probably didn't state that in the OP.

It dawned on me, that the parameter type(s) are always dependant on the return type. I could then construct a helper-struct, that would do the sfinae:

template<typename T>
    struct option_of;

template<>
    struct option_of<int>
    {
        typedef float value;
    };

template<>
    struct option_of<long>
    {
        typedef double value;
    };

and then the default template would look like this:

template<typename Ret>
    Ret func(typename const option_of<Ret>::value *);

and the overloads could then be constructed like this:

template<>
    int func(const float * in)
    {
        std::cout << "long";
    }

template<>
    long func(const double * in)
    {
        std::cout << "int";
    }

-without problems. Note that any other combination of returns and parameter types would be invalid (since they are not a specialization of the original template, which only considers the options i give it). This also reduces the only overload resolutions to the two overloads, and thus makes this possible:

wrapper<float> w;
func<int>(w); // works
func<long>(w); // invalid, because no combination of long and float exists according to option_of

wrapper<int> w2; // works, but
func<int>(w2); // invalid because option_of doesn't consider int's

The added bonus of course is the compiler recognizes the error at the call / instantiation with a correct error message, instead of some random static_assert / linker errors. Success!



回答3:

Another approach may be to use static_assert:

template<typename Ret, typename T>
Ret func(T) {
  static_assert(false, "template specialization required");
}