switch dilemma

Just fishing for some architecture suggestions...

I have a templated container that stores one of four data types.
My program users this container to interface with a whole bunch of API-like functions (I can't/won't change them). These functions are named depending on the data type they operate on and may even have different parameter lists-- even though the functions do the same thing (abstractly speaking).

See this article for an example: http://www.cplusplus.com/forum/articles/14441/

At the moment I am running the typinfo through a switch (which seems awful to me) in order to decide which funtion to call. I also have to explicitly cast the function parameters in the switch (also awful and causes problems when I try to optimize i.e. type-punning). I am wondering if there is another approach to this. I've thought about template specialization and inheritance. I don't think inheritance is a good idea though.
Last edited on
You can define some overloaded functions to replace the API functions
eg:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
return_type1 my_func ( type1 param )
{
    return api_type1_func ( param );
}

return_type2 my_func ( type2 param )
{
    return api_type2_func ( param );
}

//etc...

yourclass::function()
{
    my_func ( variable_of_type1_or_type2 );
}


You can also specialize templates for the container ( see http://www.cplusplus.com/doc/tutorial/templates/ Template specialization )
Tricky one, there's a lot to get one's head around.

Can I summarise by saying, it seems you want a matrix class that handles specific types of numbers, and there are differemt operations for each type of number, but the algorithms are the same. You want type safety and runtime efficiency that templates allow and you've found a few cludges that you want ironed out.

I think you need to use traits. The traits class would look something like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <class T>
class FMatrixTraits
{
    typename T value_type;

    static gemm(char*, char*, integer*, integer*, integer*, T*, T*, integer*, T*, integer*, T*, T*, integer*) {}
    static gesvd(
        char*, char*, integer*, integer*,
        T*, integer*,
        void*,
        T*, integer*,
        T*, integer*,
        T*, integer*, integer*, void*) {}
};


and you'll have specialisations for the four number types you've shown, like:
template <>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class FMatrixTraits<real>
{
    typename real value_type;
    typedef	real T;

    static gemm(char*p1, char*p2, integer*p3, integer*p4, integer*p5, T*p6, T*p7, integer*p8, T*p9, integer*p10, T*p11, T*p12, integer*p13)
    {
        sgemm_(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14);
    }
    static gesvd(
        char*p1, char*p2, integer*p3, integer*p4,
        T*p5, integer*p6,
        real*p7,
        T*p8, integer*p9,
        T*p10, integer*p11,
        T*p12, integer*p13, integer*p14, void*p15 = 0)
    {
        sgesvd_(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14);
    }
};


The FMatrix class should have the types added, and you'd declare one as:
typedef Fmatrix<real, MatrixTraits<real> > matrix_real;
matrix_real a;


You could then remove Fmatrix methods getype/settype, and remove member char type. You'd get the type statically with matrix_real::traits_type::value_type

Function gmm would become:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <class T, FMatixTraits<T> >
int gmm(Fmatrix<T>& A, Fmatrix<T>& B, Fmatrix<T>& C,
            char TRANSA='N', char TRANSB='N', T alpha=1, T beta=0)
{
    integer M, N, K, LDA, LDB, LDC;

    M=(TRANSA=='N') ? A.numrows():A.numcols();
    N=(TRANSB=='N') ? B.numcols():B.numrows();
    K=(TRANSA=='N') ? A.numcols():A.numrows();
    LDA=(TRANSA=='N') ? M:K;
    LDB=(TRANSB=='N') ? K:N;
    LDC=C.numrows();

    traits_type::gemm(
        &TRANSA, &TRANSB, &M, &N, &K,
         &alpha,
         A.begin(), &LDA,
         B.begin(), &LDB,
         &beta,
         C.begin(), &LDC);
   return 0;
}


and so on.

BTW, I don't understand how you managed to get it compile with your own complex class in the presence of the declaration using namespace std;
Last edited on
I would consider looking up boost::variant<> to hold the value (which you said is one of four known types)
and then using boost::static_visitor<> and boost::apply_visitor().
Thanks guys.

What I really want to get is the most minimal architecture-- eliminating repetition more precisely.

Overloading causes repetition in the function parameter list.
Template specialization might work for some things but my functions are not part of the class.
kbw: you solution is interesting but there is lots of duplication. I suppose the complex thing works because it is typdefed before I invoke using namespace std;
I may be oversimplifying but the boost variant and visitor look like fancy overloading to me... I still have to declare a function for each type.

Here is a overloaded version of gmm:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
template <class T>
void gmmSetup(const Fmatrix<T>& A, const Fmatrix<T>& B, const Fmatrix<T>& C,
                char& TRANSA, char& TRANSB,
                integer& M, integer& N, integer& K,
                integer& LDA, integer& LDB, integer& LDC)
{
    if(TRANSA  == 'T'){ M = A.numcols(); K = A.numrows(); LDA = K; }
    else{ M = A.numrows(); K = A.numcols(); LDA     = M; }
    if(TRANSB  == 'T'){ N = B.numrows(); LDB = N; }
    else{ N = B.numcols(); LDB = K;
    }
    LDC=C.numrows();
}

void gmm(Fmatrix<float>& A, Fmatrix<float>& B, Fmatrix<float>& C,
            char TRANSA='N', char TRANSB='N', float alpha=1, float beta=0)
{
    integer M, N, K, LDA, LDB, LDC;
    gmmSetup(A, B, C, TRANSA, TRANSB, M, N, K, LDA, LDB, LDC);
    sgemm_(&TRANSA, &TRANSB, &M, &N, &K,
            &alpha, A.begin(), &LDA, B.begin(), &LDB,
            &beta,  C.begin(), &LDC  );
}

void gmm(Fmatrix<double>& A, Fmatrix<double>& B, Fmatrix<double>& C,
            char TRANSA='N', char TRANSB='N', double alpha=1, double beta=0)
{
    integer M, N, K, LDA, LDB, LDC;
    gmmSetup(A, B, C, TRANSA, TRANSB, M, N, K, LDA, LDB, LDC);
    dgemm_(&TRANSA, &TRANSB, &M, &N, &K,
            &alpha, A.begin(), &LDA, B.begin(), &LDB,
            &beta,  C.begin(), &LDC  );
}

void gmm(Fmatrix<complex>& A, Fmatrix<complex>& B, Fmatrix<complex>& C,
            char TRANSA='N', char TRANSB='N', complex alpha, complex beta)
{
    integer M, N, K, LDA, LDB, LDC;
    gmmSetup(A, B, C, TRANSA, TRANSB, M, N, K, LDA, LDB, LDC);
    cgemm_(&TRANSA, &TRANSB, &M, &N, &K,
            &alpha, A.begin(), &LDA, B.begin(), &LDB,
            &beta,  C.begin(), &LDC  );
}

void gmm(Fmatrix<doublecomplex>& A, Fmatrix<doublecomplex>& B, Fmatrix<doublecomplex>& C,
            char TRANSA='N', char TRANSB='N', doublecomplex alpha, doublecomplex beta)
{
    integer M, N, K, LDA, LDB, LDC;
    gmmSetup(A, B, C, TRANSA, TRANSB, M, N, K, LDA, LDB, LDC);
    zgemm_(&TRANSA, &TRANSB, &M, &N, &K,
            &alpha, A.begin(), &LDA, B.begin(), &LDB,
            &beta,  C.begin(), &LDC  );
}

template <class T>
void gmm(Fmatrix<T>& A, Fmatrix<T>& B, Fmatrix<T>& C,
            char TRANSA='N', char TRANSB='N', T alpha=1, T beta=0)
{
    std::cout << "\nERROR: Unrecognized data Type.\n\n";
}
Last edited on
boost::static_visitor<> and boost::apply_visitor solve the problem that if you ever add an additional 5th
type to the container and forget to implement the function to handle that new type, you'll get a compile
error, whereas with a switch you'll just fall into the default case and get (at best) a runtime error.

Are you saying that you don't want to have to duplicate the actual parameter lists (ie, at the call-site)
as opposed to the formal parameters (ie, at declaration of function)?

There will never be a 5th type, but that sounds pretty handy. I'm just starting to dig into design patterns.

Well... call-site duplication is fine, that is the interface, it should be uniform. But in the case of gmm the overloaded functions are exactly the same except for s, d, c, z prefixing gemm_ . Maybe there is some way (excluding the use of the preprocessor) to combine these. It is more complicated for the other functions, they have workspaces and differing parameter lists.

Topic archived. No new replies allowed.