user7867434
user7867434

Reputation:

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;
}

Upvotes: 2

Views: 314

Answers (2)

Mike van Dyke
Mike van Dyke

Reputation: 2868

Why don't you just provide template specializations for parseFoo, parseBar and parseFoos and then just call the template method from within the static parse function:

//parseT replaces parseFoo, parseBar, parseFoos
template<typename T>
std::shared_ptr<T> parseT(const std::string & data); 

// provide implementaiton for Foo, Bar and vector<Foo>
template<>
std::shared_ptr<Foo> parseT<Foo>(const std::string & data)  { 
   return std::make_shared<Foo>(); 
}

template<>
std::shared_ptr<Bar> parseT<Bar>(const std::string & data)  { 
   return std::make_shared<Bar>(); 
}

template<>
std::shared_ptr<std::vector<Foo>> parseT<std::vector<Foo>>(const std::string & data)  { 
   return std::make_shared<std::vector<Foo>>(); 
}

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

    result = std::make_shared<T>();
    result = parseT<T>(data); // simple call to template function

    return result;
}

EDIT: Whoops, didn't read properly enough, now I see that's not what you wanted (though not really sure why, it seems to be the best option for me :D). Anyways, if you want to use tag dispatching something along the lines of the following code comes to my mind (again IMO not that nice, due to another template parameter for the parser function):

struct FooTag {};
struct BarTag{};
struct FoosTag{};

std::shared_ptr<Foo> parseT(const std::string & data, FooTag) {
    return std::make_shared<Foo>();
}

std::shared_ptr<Bar> parseT(const std::string & data, BarTag) {
    return std::make_shared<Bar>();
}

std::shared_ptr<std::vector<Foo>> parseT(const std::string & data, FoosTag) {
    return std::make_shared<std::vector<Foo>>();
}

// template version
template <typename T, typename Tag>
std::shared_ptr<T> parser(const std::string & data) {
    std::shared_ptr<T> result = nullptr;
    if (data.empty())
        return result;

    result = std::make_shared<T>();
    result = parseT(data, Tag());

    return result;
}

If you don't want the extra template parameter, you can let the user provide a tag class inside of Foo and Bar and whatsoever, but will not work when you have a vector of Foos:

// Tag is now a nested class
class Foo {
    public:
    struct Tag{};

};
class Bar {
    public:
    struct Tag{};

};

std::shared_ptr<Foo> parseT(const std::string & data, Foo::Tag) {
    return std::make_shared<Foo>();
}

std::shared_ptr<Bar> parseT(const std::string & data, Bar::Tag) {
    return std::make_shared<Bar>();
}


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

    result = std::make_shared<T>();
    result = parseT(data, T::Tag()); // tag is now inside of template parameter

    return result;
}

Another EDIT: You could make a template class for the Tag, in order to get rid of the extra template parameter in the parser function

template <typename T>
struct Tag{};

std::shared_ptr<Foo> parseT(const std::string & data, Tag<Foo>) {
    return std::make_shared<Foo>();
}

std::shared_ptr<Bar> parseT(const std::string & data, Tag<Bar>) {
    return std::make_shared<Bar>();
}


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

    result = std::make_shared<T>();
    result = parseT(data, Tag<T>{});

    return result;
}

Upvotes: 1

Jarod42
Jarod42

Reputation: 217358

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);
}

Upvotes: 2

Related Questions