Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different code path according to different type in a template method

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++.

like image 717
Cheng Chen Avatar asked Dec 06 '17 09:12

Cheng Chen


People also ask

What is template and its types?

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.

What are templates coding?

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.

What is a template method in C++?

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.


1 Answers

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.

like image 145
Vittorio Romeo Avatar answered Oct 17 '22 15:10

Vittorio Romeo