Reputation: 1207
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
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
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