Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specialized template dispatch independent of template parameter position

I have come across the problem of needing a template function that will have the same output so long as it's template parameters are the same, independent of position. Given that there are always two parameters.

I have a function:

template<typename Lhs, typename Rhs>
int func();

I would like func<int, float>() and func<float, int>() to call the same code.

I thought about a macro, which I like to avoid, but I need to not duplicate code when the two types are the same. So a macro such as:

#define DEF_TEMPL_IMPL(lhs, rhs, ret) \
template<>\
auto func<lhs, rhs>(){ return ret; }\
template<>\
auto func<rhs, lhs>(){ return func<lhs, rhs>(); }

will fail to compile because DEF_TEMPL_IMPL(float, float, 3) would cause a redefinition of func<>

I think SFINAE is the answer here, but can't quite think of a solution.

I will continue to ponder this, but some of the great minds of stack overflow might have a much better or more elegant solution than I can come up with before this question is answered.

So how can this be achieved?

like image 965
RamblingMad Avatar asked May 11 '16 08:05

RamblingMad


3 Answers

You could just write one specialization for each type pair, then make your primary template delegate to func<Rhs,Lhs>() if it gets called:

//This will get called if no specializations match
template<typename Lhs, typename Rhs>
int func() {
    //Check if there is a specialization for the flipped pair
    return func<Rhs, Lhs>();   
}

//This will get called for <int,float> and <float,int>
template <>
int func<int,float>() {
    return 42;   
}

//Ditto for <bool,float> and <float,bool>
template <>
int func<bool,float>() {
    return 0xbeef;   
}

//Specializations with the same arguments are also supported by this scheme
template <>
int func<bool,bool>() {
    return 12;   
}

Live Demo

like image 64
TartanLlama Avatar answered Sep 26 '22 01:09

TartanLlama


Here's another possible solution

template<typename A, typename B>
struct List { };

template<typename A, typename B>
struct Set { 
   Set(List<A, B>) { }
   Set(List<B, A>) { }
};

template<typename A>
struct Set<A, A> { 
   Set(List<A, A>) { }
};

Now, write each as an overload for Set<A, B>, like this

template<typename Lhs, typename Rhs>
int func() {
    return func(List<Lhs, Rhs>());
}

int func(Set<int, float>) {
    return 42;   
}

int func(Set<bool, float>) {
    return 0xbeef;   
}

int func(Set<bool, bool>) {
    return 12;   
}

This way, you can also define func(List<float, int>), if for some reason, the order for float, int is important. If you accidentally defined both Set<int,float> and Set<float, int>, then the call would be ambiguous.

You can also have it validate at the time of definition of the overloads that you don't have duplicate Set<A, B> vs Set<B, A> defined by changing the definition of the binary Set to this

template<typename A, typename B>
struct Set { 
   Set(List<A, B>) { }
   Set(List<B, A>) { }

   friend void dupeCheck(List<A, B>) { }
   friend void dupeCheck(List<B, A>) { }
};

This however is not strictly necessary for this trick. Especially since the types in your JIT compiler may be easily manually ordered in a consistent way.

like image 28
Johannes Schaub - litb Avatar answered Sep 25 '22 01:09

Johannes Schaub - litb


You can define the order of types (i.e. int - 1, float - 2, char - 3). Then implement a function that will sort these two types:

#include <iostream>

template<typename T>
struct Order;

// There should be a better solution for getting type id in compile time
// This will let you specify the order
template<>
struct Order<int>
{
    enum { Value = 1 };
};

template<>
struct Order<float>
{
    enum { Value = 2 };
};

template<>
struct Order<char>
{
    enum { Value = 3 };
};

template<typename A, typename B, bool swap>
struct Sort;

template<typename A, typename B>
struct Sort<A, B, false>
{
    typedef A First;
    typedef B Second;
};

template<typename A, typename B>
struct Sort<A, B, true>
{
    typedef B First;
    typedef A Second;
};


template<typename Lhs, typename Rhs>
int func_sorted()
{
    return 1;
}

template<>
int func_sorted<int, float>()
{
    return 2;
}

template<>
int func_sorted<float, int>()
{
    return 3;
}


template<typename Lhs, typename Rhs>
int func()
{
    typedef typename Sort<
        Lhs,
        Rhs,
        ((int)Order<Lhs>::Value > (int)Order<Rhs>::Value)>::First First;
    typedef typename Sort<
        Lhs,
        Rhs,
        ((int)Order<Lhs>::Value > (int)Order<Rhs>::Value)>::Second Second;
    return func_sorted<First, Second>();
}

so following code

int main()
{
    std::cout << func<int, float>() << std::endl;
    std::cout << func<float, int>() << std::endl;
}

will print:

2
2

like image 41
Teivaz Avatar answered Sep 27 '22 01:09

Teivaz