1

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.

SaldaVonSchwartz
  • 3,769
  • 2
  • 41
  • 78
  • I don't understand why you're seeing linker errors, but your idea isn't going to work even otherwise because `enable_if` works via [SFINAE](https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error). In your use case there cannot be any substitution failure because there is no template parameter substitution happening to begin with. – Praetorian Sep 01 '15 at 02:58
  • @Praetorian I updated my question with an alternative approach I tried, where I assumed I would be substituting over the whole class. But I get the same result. – SaldaVonSchwartz Sep 01 '15 at 03:04
  • 1
    Please avoid needless macros mixed in with your template metaprogramming. Both are complex subsystems of C++, and mixing the two can and will cause cascades of garbage. Plus you have macros that presume a token not passed to it has some specific meaning, for extra awefulness, which in at least one case above causes your errors to occur. "I am juggling chainsaws, and to make it easier I set them on fire and attached dynamite to them to improve their balance, and I keep getting hurt"... After you have things working, *consider* macros, then discard them: don't start with them. – Yakk - Adam Nevraumont Sep 01 '15 at 03:20
  • 1
    [Here's](http://coliru.stacked-crooked.com/a/f34cd417c0a4ce29) a working version of your partial specialization idea. dyp's excellent answer [here](https://stackoverflow.com/a/27688405/241631) explains how it works. – Praetorian Sep 01 '15 at 05:06

1 Answers1

1

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!

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Not sure I follow. Should I be able to use your form inside the single class in-line definition for all 3 versions of the method? or should I do 3 separate class specializations each with their own version of 'update'? Also, the update method should take no parameters, but the T you put in there I presume is the class one as opposed to the method template type, in which case now I'd be stuck with an extra unused param? (I could make it default I guess). – SaldaVonSchwartz Sep 01 '15 at 03:20
  • @SaldaVonSchwartz Your declarations (if they worked) have `T` as the parameter type, not `void`. It took me a minute to realize that, so my initial answer explained why you cannot put `void` there. I'll restore that to the answer. – Potatoswatter Sep 01 '15 at 03:22
  • Yes, there's a separate problem with overloading. I'll clarify that too. Hmm, I wonder if there's an existing question that covers all these issues. – Potatoswatter Sep 01 '15 at 03:23
  • Basically my issue is that I'm porting some Objective-C code from a little 3d engine I made a while back to C++. This code is related to encapsulating setting shader values thru the GL C-API. In obj-c there are not generics but I can instead go for a method taking the 'id' type which is void* and can still safely test it for membership. Here, depending on the type, I need to call a certain gl function and cast the T value in the class to a raw pointer based on glm's value_ptr function. The function won't let me stick in a generic T var. I'm trying to avoid overloading the method for every type – SaldaVonSchwartz Sep 01 '15 at 03:31
  • @SaldaVonSchwartz You don't need to make an excuse… it's the language's fault. Let me know if the current answer doesn't work. – Potatoswatter Sep 01 '15 at 03:33
  • with your last approach when I attempt to call 'update' on a concrete instance it doesn't find the matching function. That is: `Descriptor* descriptor = new Descriptor(); descriptor->update(); // no matching member function` – SaldaVonSchwartz Sep 01 '15 at 03:47
  • Can you think of a better approach all together if not, other than explicitly writing all 16 overloads of the method? – SaldaVonSchwartz Sep 01 '15 at 03:47
  • @SaldaVonSchwartz Working now: http://coliru.stacked-crooked.com/a/64e957e0bb29cde9 Looks like there's a Clang bug with the comma operator, which I forgot to remove when updating the macro. – Potatoswatter Sep 01 '15 at 03:59
  • Nice. Thanks! But still, let's assume this is a bad idea to start with. How else could I go about it in C++ short of overloading explicitly for every supported type? – SaldaVonSchwartz Sep 01 '15 at 04:13
  • 1
    [This](http://coliru.stacked-crooked.com/a/aeb61d4ee82335eb) is arguably a bit cleaner because you don't have to use distinct types for each overload. /cc @SaldaVonSchwartz – Praetorian Sep 01 '15 at 04:38