Let's say I have a template class like this:
template<typename TRequest, typename TResponse = void>
class handler
{
private:
TResponse process_core(const TRequest& request);
public:
TResponse process(const TRequest& request)
{
//log the request
TResponse response = process_core(request); //return process_core(request) works;
//log the response, for void it's fine to log nothing
return response;
}
};
Somewhere else in the project process_core
is implemented for different TRequest/TResponse types. For example:
template<> void handler<Foo>::process_core(const Foo& foo) { }
template<> Baz handler<Bar, Baz>::process_core(const Bar& bar) { }
Obviously return response
breaks for void
types. What's the correct way to do this? Or my design is not the C++ way? I'm new to C++.
A template is a simple yet very powerful tool in C++. The simple idea is to pass data type as a parameter so that we don't need to write the same code for different data types. For example, a software company may need to sort() for different data types.
Templates in c++ is defined as a blueprint or formula for creating a generic class or a function. Generic Programming is an approach to programming where generic types are used as parameters in algorithms to work for a variety of data types.In C++, a template is a straightforward yet effective tool.
Template Method in C++ Template Method is a behavioral design pattern that allows you to defines a skeleton of an algorithm in a base class and let subclasses override the steps without changing the overall algorithm's structure.
Unfortunately void
is not a regular type, even though there's a proposal that aims to fix that ("regular void" by Matt Calabrese), so you need to handle it in a special way. With C++17, you can simply use if constexpr(...)
to branch at compile-time:
TResponse process(const TRequest& request)
{
TResponse response = process_core(request);
// ...
if constexpr(!std::is_same_v<TResponse, void>)
{
return response;
}
}
With C++11/14, you can use tag dispatching:
TResponse process(const TRequest& request)
{
return processImpl(request, std::is_same<TResponse, void>{});
}
void process(const TRequest& request, std::true_type /* void */)
{
TResponse response = process_core(request);
// ...
}
TResponse process(const TRequest& request, std::false_type /* not void */)
{
TResponse response = process_core(request);
// ...
return response;
}
Alternatively, you can transform void
into a regular nothing
type and handle it homogeneously.
struct nothing { };
template <typename T>
struct void_to_nothing { using type = T; };
template <>
struct void_to_nothing<void> { using type = nothing; };
template <typename T>
using void_to_nothing_t = typename void_to_nothing<T>::type;
auto process(const TRequest& request)
{
void_to_nothing_t<TResponse> response = process_core(request);
// ...
return response;
}
Note that process_core
must return nothing
instead of void
in this case, so you'll need some sort of specialization or compile-time branching anyway.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With