C++ 模板 "if constexpr" 到旧的 "Tag Dispatching" 方法
C++ Template "if constexpr" into the old "Tag Dispatching" method
我对 SFINAE
或 tag dispatching
等模板概念还很陌生,并且一直在阅读一些关于它的文章和示例,但它们对我的方法没有帮助。所以,如果有人能提供帮助,我将不胜感激。
我的目标是拥有 1 个单独的解析函数,它会在将数据转发给其他函数以根据模板 T
类型进行特定解析之前做一些事情。
在附加的代码中,这是我想要的行为。我在这里使用 if constexpr
,不幸的是,这是一个 C++17 功能,在我使用的 C++ 版本中不可用。
为此,我认为乍一看 template specialization
是最好的解决方案,但这不是我想要的。
我认为 tag dispatching
是一个很好的方向,但我不确定当我有自定义类型时如何用 type_traits
做到这一点,因为它总是像我有 2 个选项,一个 true_type
或 false_type
,但在下面的代码中,我有 3 种可能有更多的情况。
我非常感谢您提供一些示例或说明,以了解什么是完成我正在寻找的事情的最佳方法。即使是一些文章阅读也会很棒。
提前致谢!
工作代码示例:
#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;
}
为什么不为 parseFoo
、parseBar
和 parseFoos
提供模板特化,然后从静态 parse
函数中调用模板方法:
//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;
}
编辑:糟糕,读得不够正确,现在我明白这不是你想要的(虽然不确定为什么,但它似乎是我的最佳选择 :D)。无论如何,如果你想使用标签调度,我会想到以下代码(IMO 也不是很好,因为 parser
函数的另一个模板参数):
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;
}
如果你不想要额外的模板参数,你可以让用户在 Foo
和 Bar
内提供一个标签 class 等等,但是当你有 vector
的 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;
}
另一个编辑:
您可以为 Tag 创建一个模板 class,以摆脱 parser
函数
中的额外模板参数
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;
}
标签调度会让这里更容易:
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);
}
我对 SFINAE
或 tag dispatching
等模板概念还很陌生,并且一直在阅读一些关于它的文章和示例,但它们对我的方法没有帮助。所以,如果有人能提供帮助,我将不胜感激。
我的目标是拥有 1 个单独的解析函数,它会在将数据转发给其他函数以根据模板 T
类型进行特定解析之前做一些事情。
在附加的代码中,这是我想要的行为。我在这里使用 if constexpr
,不幸的是,这是一个 C++17 功能,在我使用的 C++ 版本中不可用。
为此,我认为乍一看 template specialization
是最好的解决方案,但这不是我想要的。
我认为 tag dispatching
是一个很好的方向,但我不确定当我有自定义类型时如何用 type_traits
做到这一点,因为它总是像我有 2 个选项,一个 true_type
或 false_type
,但在下面的代码中,我有 3 种可能有更多的情况。
我非常感谢您提供一些示例或说明,以了解什么是完成我正在寻找的事情的最佳方法。即使是一些文章阅读也会很棒。
提前致谢!
工作代码示例:
#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;
}
为什么不为 parseFoo
、parseBar
和 parseFoos
提供模板特化,然后从静态 parse
函数中调用模板方法:
//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;
}
编辑:糟糕,读得不够正确,现在我明白这不是你想要的(虽然不确定为什么,但它似乎是我的最佳选择 :D)。无论如何,如果你想使用标签调度,我会想到以下代码(IMO 也不是很好,因为 parser
函数的另一个模板参数):
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;
}
如果你不想要额外的模板参数,你可以让用户在 Foo
和 Bar
内提供一个标签 class 等等,但是当你有 vector
的 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;
}
另一个编辑:
您可以为 Tag 创建一个模板 class,以摆脱 parser
函数
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;
}
标签调度会让这里更容易:
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);
}