Reputation: 38897
I have some functions that work on a type defined by a C library.
The types are float_2, float_3, float_4, int32_2, int32_3, int32_4.
These types have something in common, a field named size. For float_2, size = 2. float_3, size = 3 etc.
Right now I have a template specialization for every single type.
static void add_number(rapidjson::Value &target, const char* name, float_2 src, Document::AllocatorType& alloc)
{
std::ostringstream ss;
ss << src.x << " " << src.y;
std::string s(ss.str());
target.AddMember(StringRef(name), s, alloc);
}
static void add_number(rapidjson::Value &target, const char* name, float_3 src, Document::AllocatorType& alloc)
{
std::ostringstream ss;
ss << src.x << " " << src.y << " " << src.z;
std::string s(ss.str());
target.AddMember(StringRef(name), s, alloc);
}
It would sure be nice to be able to do the following:
template <typename T>
static void add_number(rapidjson::Value &target, const char* name, T src, Document::AllocatorType& alloc)
{
std::ostringstream ss;
switch(src.size){
case 2: ss << src.x << " " << src.y;
case 3: ss << src.x << " " << src.y << " " << src.z;
case 4: ss << src.x << " " << src.y << " " << src.z << " " << src.w;
}
std::string s(ss.str());
target.AddMember(StringRef(name), s, alloc);
}
But that doesn't work because these fields don't exist for all the types defined. Is there some C++ magic template syntax that would allow me to avoid specializing every single case?
Upvotes: 1
Views: 139
Reputation: 24946
You can indeed use "template magic" to (remove function templates from the) overload (set). If there aren't more than about ten different types I'd rather go for operator<<
overloads, however.
namespace detail
{
template <class T, class = int> struct has_z : std::false_type { };
template <class T> struct has_z <T, decltype((void)T::z, 0)> : std::true_type { };
template <typename T>
static auto foo_impl(T src)
-> std::enable_if_t<!has_z<T>::value>
{
std::cout << "1: " << src.x << " " << src.y << "\n";
}
template <typename T>
static auto foo_impl(T src)
-> std::enable_if_t<has_z<T>::value>
{
std::cout << "2: " << src.x << " " << src.y << " " << src.z << "\n";
}
}
template<class T>
void foo(T src)
{
detail::foo_impl(src);
}
Now you can use foo
on every type that has an x
, a y
and optionally a z
member:
struct A { double x{ 1 }, y{ 2 }; };
struct B { double x{ 3 }, y{ 4 }, z{ 5 }; };
int main() {
A a;
B b;
foo(a);
foo(b);
return 0;
}
prints
1 2
3 4 5
Upvotes: 1
Reputation: 46
I'm not sure whether this has to do with printing alone, size or any other specialization requirement. However, I think the least amount of specialization for the case presented is overloading the streaming operator for the different cases. That would look like this:
std::ostream& operator<<(std::ostream &stream, const float_2 &vec)
{
return stream << vec.x << ", " << vec.y;
}
std::ostream& operator<<(std::ostream &stream, const float_3 &vec)
{
return stream << vec.x << ", " << vec.y << ", " << vec.z;
}
std::ostream& operator<<(std::ostream &stream, const float_4 &vec)
{
return stream << vec.x << ", " << vec.y << ", " << vec.z << ", " << vec.w;
}
template < typename T >
static void add_number(rapidjson::Value &target, const char* name, const T &src, Document::AllocatorType& alloc)
{
std::ostringstream ss;
ss << src;
std::string s(ss.str());
target.AddMember(StringRef(name), s, alloc);
}
Upvotes: 3