YotKay
YotKay

Reputation: 1207

Enable_if for template function specialization

I was playing with Decorator pattern in C++. After making things work like in a book I decided to create a template function (called 'decorate') to help me construct the decorated object based on the decorator class, concrete class and interface class.

Basic template function worked perfect. But then I decided that I would like to have also an empty implementation of decorator class, which would not do anything else but calling the concrete object method. This worked and I could stick to this design, but I wasn't very satisfied by the fact that when the empty decorator is used, I would still have a virtual call there, which in fact does nothing but calling another virtual method. I wanted to avoid such unnecessary indirection and I decided to specialize my template function so that when the empty decorator is used, it returns undecorated object (concrete object). I wanted to specialize my template function using enable_if but somehow it doesn't want to compile. I must be doing something wrong. Please help me. What is wrong with my code below?

#include <iostream>
#include <memory>
#include <type_traits>
using namespace std;

struct IA {
    virtual ~IA() {}
    virtual void doSth() = 0;
};

struct ConcreteA : public IA
{
    void doSth() override 
    {
         cout << "concrete: doSth" << endl;
    }
};

struct LoggingDecoratorA : public IA {
    LoggingDecoratorA(unique_ptr<IA> pia) : _pia(move(pia)) {}
    void doSth() override 
    {
         cout << "calling doSth" << endl;
        _pia->doSth();
    }
    unique_ptr<IA> _pia;
};

struct EmptyDecoratorA : public IA {
    EmptyDecoratorA(unique_ptr<IA> pia) : _pia(move(pia)) {}
    void doSth() override 
    {
        _pia->doSth();
    }
    unique_ptr<IA> _pia;
};

void fun(IA* pia)
{
    pia->doSth();
}

template<typename Decorator, typename Concrete, typename Interface, typename X = enable_if_t<!is_same<Decorator, EmptyDecoratorA>::value>>
unique_ptr<Interface> decorate()
{
    return make_unique<Decorator>((make_unique<Concrete>()));
}

template<typename Decorator, typename Concrete, typename Interface, typename X = void>
unique_ptr<Interface> decorate()
{
    return make_unique<Concrete>();
}

int main()
{ 
    auto pia = decorate<LoggingDecoratorA, ConcreteA, IA>();
    fun(pia.get());
}

Upvotes: 4

Views: 2619

Answers (2)

Edgar Rokjān
Edgar Rokjān

Reputation: 17483

template<typename Decorator, typename Concrete, typename Interface,
    typename X = enable_if_t<!is_same<Decorator, EmptyDecoratorA>::value>>
unique_ptr<Interface> decorate()
{
    return make_unique<Decorator>((make_unique<Concrete>()));
}

template<typename Decorator, typename Concrete, typename Interface,
    typename X = void>
unique_ptr<Interface> decorate()
{
    return make_unique<Concrete>();
}

That is not how SFINAE work (default template type parameter is not a part of a function signature, check this question for more details).

You might change the code as follows:

template<typename Decorator, typename Concrete, typename Interface>
auto decorate() ->
    enable_if_t<!is_same<Decorator, EmptyDecoratorA>::value, unique_ptr<Interface>>
{
    return make_unique<Decorator>((make_unique<Concrete>()));
}


template<typename Decorator, typename Concrete, typename Interface>
auto decorate() ->
    enable_if_t<is_same<Decorator, EmptyDecoratorA>::value, unique_ptr<Interface>>
{
    return make_unique<Concrete>();
}

and move std::enable_if_t to the return type. Now SFINAE is applied correctly because return type is a part of a function signature.

Upvotes: 1

p-a-o-l-o
p-a-o-l-o

Reputation: 10079

Try this:

template<typename Decorator, typename Concrete, typename Interface>
typename enable_if<is_same<Decorator, EmptyDecoratorA>::value, unique_ptr<Interface>>::type
decorate()
{
    return make_unique<Decorator>((make_unique<Concrete>()));
}

template<typename Decorator, typename Concrete, typename Interface>
typename enable_if<!is_same<Decorator, EmptyDecoratorA>::value, unique_ptr<Interface>>::type
decorate()
{
    return make_unique<Concrete>();
}

Upvotes: 1

Related Questions