Reputation:
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
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 Foo
s:
// 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
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