Search code examples
c++templatesc++17tag-dispatching

C++ Template "if constexpr" into the old "Tag Dispatching" method


I'm pretty new to template concepts like SFINAE or tag dispatching and been reading some articles and examples about it that didn't help me getting to my approach. So I would really appreciate if someone can help please.

My goal is to have 1 single parse function that will do some stuff before passing forward the data to some other functions to do specific parsing depending on the template T type.
In the attached code, this is kind of the behavior I want to have. I use here if constexpr which unfortunately is a C++17 feature that is not available in the C++ version I use.
I think for that purpose it looks like in first look template specialization would be the best solution, but it's not what I wanted.
I think for that purpose tag dispatching would be a good direction, but I'm not sure how exactly to do that with type_traits when I have custom types, as it's always like I have 2 options, a true_type or false_type, but in the following code I have 3 situation with potential of having more.

I would really appreciate for some examples or directions please of what's the best approach to do what I'm looking for. Even some article to read would be great.

Thanks in advance!

Working code example:

#include <string>
#include <vector>
#include <memory>
using namespace std;

struct Base       { int id; };
struct Foo : Base { int fooValue; };
struct Bar : Base { int barValue; };

shared_ptr<Foo>         parseFoo(const string & data)  { return make_shared<Foo>(); }
shared_ptr<Bar>         parseBar(const string & data)  { return make_shared<Bar>(); }
shared_ptr<vector<Foo>> parseFoos(const string & data) { return make_shared<vector<Foo>>(); }

template <typename T>
shared_ptr<T> parse(const std::string & data)
{
    shared_ptr<T> result = nullptr;
    if (data.empty())
        return result;

    result = make_shared<T>();
    if constexpr      (std::is_same<T, Foo>::value)         result = parseFoo(data);
    else if constexpr (std::is_same<T, Bar>::value)         result = parseBar(data);
    else if constexpr (std::is_same<T, vector<Foo>>::value) result = parseFoos(data);

    return result;
}

int main()
{
    string data = "some json response";
    auto foo = parse<Foo>(data);
    auto bar = parse<Bar>(data);
    auto foos = parse<vector<Foo>>(data);

    return 0;
}

Solution

  • Tag dispatching would make it easier here:

    struct Base       { int id; };
    struct Foo : Base { int fooValue; };
    struct Bar : Base { int barValue; };
    
    template <typename T> struct Tag {};
    
    std::shared_ptr<Foo> parse_impl(Tag<Foo>, const std::string& data)  { return make_shared<Foo>(); }
    std::shared_ptr<Bar> parse_impl(Tag<Bar>, const std::string& data)  { return make_shared<Bar>(); }
    std::shared_ptr<std::vector<Foo>> parse_impl(Tag<std::vector<Foo>>, const std::string& data)
    {
        return make_shared<std::vector<Foo>>();
    }
    
    template <typename T>
    std::shared_ptr<T> parse(const std::string& data)
    {
        if (data.empty())
            return nullptr;
        return parse_impl(Tag<T>{}, data);
    }