2

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;
}
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • not really sure if I understand the problem. If `parseFoo` etc would be a template, lets say `doParse` then in `parse` would would simply call `doParse`. Then for each type `X` that you want to be parsable you just need to write a `doParse` – 463035818_is_not_an_ai Oct 23 '18 at 10:25
  • btw its not clear why you need both `parse` and `parseFoo`. `parse` seems to do nothing but return the result of the call to `parseFoo` – 463035818_is_not_an_ai Oct 23 '18 at 10:26
  • @user463035818 Thanks for asking! Basically imagine I have a `Parser` class implementation with `static parse` template function that is public, the users who use the library, will only know about `parse` function, as the `parseFoo`, `parseBar` etc...will be private functions of the `Parser` object. –  Oct 23 '18 at 10:31
  • @GiladReich You could always make the Parser class a template, and partially specialize it. Anothe rsolution would be to overload the foo method using std::enable_if to choose between implementations. – Petok Lorand Oct 23 '18 at 10:35
  • @Lorand, thanks for the suggestion! I actually already though about this, but this means I'll need to have to create an instance of that parser object every time which in terms of memory it wouldn't be very efficient. That's why I used the `singleton pattern` to have one shared instance that is always available through the library/application. –  Oct 23 '18 at 10:42
  • 1
    @GiladReich You could make the member function be static, thus no instance would be required to be able to use it. The class is needed just for the partial specialization feature. – Petok Lorand Oct 23 '18 at 10:44
  • @Lorand, I like that idea! I'll consider this one. I was forcing to create an instance of the class in order to do some house keeping if needed and another reason that only if parse is called to create an instance. But since it's really central function that would be called 95% of the time, I like your idea. Thanks for that! –  Oct 23 '18 at 10:51
  • ", as the parseFoo, parseBar etc...will be private functions of the Parser object" then how is the user supposed to provide a `parseFooBar` for their custom `FooBar` type? – 463035818_is_not_an_ai Oct 23 '18 at 11:19
  • can you please fix your code? It has [at least includes missing and maybe other errors](https://ideone.com/hHwGRw) – 463035818_is_not_an_ai Oct 23 '18 at 11:21
  • @user463035818 It looks like you haven't enabled C++17 in the compiler there and that's why he doesn't like that line of code: `22:8: error: expected ‘(’ before ‘constexpr’` –  Oct 23 '18 at 11:34
  • 1
    oh right, I added the missing include in your example – 463035818_is_not_an_ai Oct 23 '18 at 11:36
  • @user463035818 Thanks! –  Oct 23 '18 at 11:42

2 Answers2

2

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);
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • this is exactly what I was looking for! you really nailed it. Simple and easy to use and extendable interface without having to re-write things too much. Thank you so much for the help! –  Oct 23 '18 at 12:16
1

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;
}
Mike van Dyke
  • 2,724
  • 3
  • 16
  • 31
  • Previously I did something similar, but without another template function which was kind of trouble maker for me, but I like your idea and will give it a shoot soon and let you know! Thanks! :) –  Oct 23 '18 at 11:41
  • 1
    @GiladReich yep, sorry. haven't read your post properly. I added a version with Tag Dispatching, but as I say in the edit, I don't like it that much. – Mike van Dyke Oct 23 '18 at 11:51
  • 1
    Loved you solution! It really helped me understanding better how to work with custom types when dispatching. Thanks a lot for that! –  Oct 23 '18 at 12:15
  • Just a note: your `parseT` functions are not renamed to `doParse` in your last two examples. – jonspaceharper Oct 23 '18 at 12:25