hookenz
hookenz

Reputation: 38897

How can I avoiding repetitive template specialization?

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

Answers (2)

Pixelchemist
Pixelchemist

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

Nikolaos Kyriazis
Nikolaos Kyriazis

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

Related Questions