Search code examples
c++templatesc++11sfinaeenable-if

I can't seem to instantiate class specializations with enable_if


I'm trying to make a class that has 3 possible versions of a template method implementation, depending on the template type being of one of three 'type sets'. Also, I'm trying to keep instances of these objects in a map.

So my initial attempt was:

class DescriptorType {
public:
    int id;
    DescriptorType() : id(-1) {}
    virtual ~DescriptorType() {}
};

template <typename T>
class Descriptor : public DescriptorType {
public:
    T value;

    void update(TYPE_CONSTRAINT((TYPE(int) || TYPE(float)))) {
        // specific implementation for these types
    }

    void update(TYPE_CONSTRAINT((TYPE(vec2) || TYPE(vec3) || TYPE(vec4)))) {
        // specific implementation for these types
    }

    void update(TYPE_CONSTRAINT((TYPE(mat2) || TYPE(mat3) || TYPE(mat4)))) {
        // specific implementation for these types
    }
};

(I have an include file with the following -- for the macros):

template <bool B, typename T = void>
using enable_if_t = typename std::enable_if<B, T>::type;

#define TYPE_CONSTRAINT(x) enable_if_t<x, T>
#define TYPE(x) std::is_same<x, T>::value

But it doesn't seem to work. It compiles but the minute I try to instantiate a Descriptor<T> for any T, be it the generic T or a concrete type like 'int' I get linker errors for all possible types (16 in my case).

My guess is I'm using enable_if wrong. But I'm not sure how.

UPDATE:

I also tried this approach with same results:

template <typename T, class Enable = void>
class Descriptor : public DescriptorType {
public:
    T value;
    void update();
};

template <typename T>
class Descriptor<T, TYPE_CONSTRAINT((TYPE(int) || TYPE(float)))> : public DescriptorType {
public:
    T value;

    void update() {
        //...
    }
};

template <typename T>
class Descriptor<T, TYPE_CONSTRAINT((TYPE(vec2) || TYPE(vec3) || TYPE(vec4)))> : public DescriptorType {
public:
    T value;

    void update() {
       //...
    }
};

template <typename T>
class Descriptor<T, TYPE_CONSTRAINT((TYPE(mat2) || TYPE(mat3) || TYPE(mat4)))> : public DescriptorType {
public:
    T value;

    void update() {
        //...
    }
};

But get the same linker errors.


Solution

  • The syntax foo(void) to declare a function with no parameters is strictly for C compatibility. It doesn't work when void comes from template substitution. (You're not actually getting void, but per the comments it was your intent.)

    SFINAE only works with template parameters in the immediate declaration. You cannot disable member functions using parameters of the class template. (This rule is very annoying, but not even the massive concepts proposal has suggested to change it.)

    To disable members based on the class template parameters, you need to add a bogus template parameter, and then make the SFINAE appear to depend on it. SFINAE can also be safely placed in template parameter default arguments:

    #define TYPE_CONSTRAINT(x) enable_if_t< (bogus(), x) >
    
    
    template< typename bogus = void,
        typename = TYPE_CONSTRAINT((TYPE(int) || TYPE(float))) >
    void update() {
    

    There's still the remaining issue of overloading. SFINAE happens at overload resolution time, but overloads are checked for mutual compatibility as they are declared. SFINAE alone is not enough to grant compatibility to different functions, so you'll need to defeat that safety mechanism too.

    #define TYPE_CONSTRAINT(x) enable_if_t< x, bogus >
    
    template< typename bogus = void,
        void * = (TYPE_CONSTRAINT((TYPE(int) || TYPE(float))) *) nullptr >
    void update() …
    
    
    template< typename bogus = int, // Use different types here
        int * = (TYPE_CONSTRAINT((TYPE(vec2) || TYPE(vec3))) *) nullptr >
    void update() …
    
    
    template< typename bogus = char,
        char * = (TYPE_CONSTRAINT((TYPE(mat2) || TYPE(mat3))) *) nullptr >
    void update() …
    

    http://coliru.stacked-crooked.com/a/64e957e0bb29cde9

    Seriously, this is the workaround. On the bright side, you have visited the darkest corner of SFINAE and lived to tell the tale!