views:

329

answers:

4

I already asked two questions related to what I'm trying to do (one resolved, one of which I will close soon). I know that C++ template instantiation does not allow any implicit conversions (see for example this comment), but I would like to simulate it.

Suppose I have the following skeleton code:

template <class T>
struct Base_A{
    virtual void interface_func() const = 0;
};
template <class T>
struct Derived_A : public Base_A<T>{
    typedef T value_type;
    void interface_func() const{}
};

template <class T>
struct Base_B{
    virtual void interface_func() = 0; // note: non-const
};
template <class T>
struct Derived_B : public Base_B<T>{
    typedef T value_type;
    void interface_func(){}
};

template <class BType>
struct Adapter : public Base_A<typename BType::value_type>{
    BType &ref_B;
    Adapter(BType &inst_B):ref_B(B_inst){}
    void interface_func() const{} // does stuff with ref_B to simulate an A
};

template <class Should_Always_Be_Base_A>
void f(const Should_Always_Be_Base_A &arg){
    // Only Base_A can be passed in by const ref
    // Passing in a Base_B by const ref would not work.
}

Derived_A<int> A;
Derived_B<int> B;
f(A); // passes in A by const ref
f(B); // I want to pass in Adapter<Derived_B<int> >(B)

I want the template parameter for function f to always be a derived class of Base_A or an Adapter. The answer to restricting the type of arg can be done, but the implicit conversion to an Adapter cannot. Is there any way to do this? The net result is I want to be able to call f as f(A) or f(B), and in both cases I need to know the actual derived type of A or B within f (f cannot just see a reference to the base class).

Aside:

Presently, I just have f(A) working, and f(B) actually calls an overload which performs the Adapter construction, but I have other functions which take N arguments, each of which can be A or B, so I would need 2^N overloads.

For the curious, this is in application to the matrix library I'm working on. Base_A represents the base matrix type, and Base_B represents the base matrix-view type. For operations which will modify a matrix argument, I need to pass in the matrix by non-const reference or a modifiable matrix-view by const-ref. The adapter is just a trivial matrix-to-view adapter. So for example, I currently have a function like

Scale(const MatrixViewBase<T> &Mview, const T &scale_factor){
    // does the actual work
}
Scale(MatrixBase<T> &M, const T &scale_factor){
    Scale(Adapter<MatrixBase<T> >(M), scale_factor);
}

It's tedious and error prone to make 2^N copies of all these functions just to create the needed overloads to handle both views and non-views. As is, this is not good enough since I want Scale to be able to know the full derived type of Mview, not just the base class, because I will potentially generate instances of types dependent on Mview.

Edit 1: Changed all B types to have non-const interface functions. This was the original intent, so apologies for any confusion.

Edit 2: Have this working code, still requires 2^N overloads, but I can live with it unless someone suggests how to deal with that.

#include <iostream>

template <class T>
struct ReadableMatrix{
    typedef T value_type;
};
template <class T>
struct WritableMatrix{
    typedef T value_type;
};
template <class T>
struct WritableMatrixView{
    typedef T value_type;
};

template <class T>
struct Matrix : public WritableMatrix<T>{
    typedef T value_type;
    typedef ReadableMatrix<T> readable_matrix;
    typedef WritableMatrix<T> writable_matrix;
};

template <class T>
struct MatrixView : public WritableMatrixView<T>{
    typedef T value_type;
    typedef ReadableMatrix<T> readable_matrix; // not really used; needs an adapter before using
    typedef WritableMatrixView<T> writable_matrix;
};

template <class T, class R>
struct IsReadableMatrix{
};
template <class T, class R>
struct IsReadableMatrix<ReadableMatrix<T>, R>{
    typedef R type;
};

template <class T, class R>
struct IsWritableMatrix{
};
template <class T, class R>
struct IsWritableMatrix<WritableMatrix<T>, R>{
    typedef R type;
};

template <class T, class R>
struct IsWritableMatrixView{
};
template <class T, class R>
struct IsWritableMatrixView<WritableMatrixView<T>, R>{
    typedef R type;
};

template <class TA, class TB>
    typename IsReadableMatrix<typename TA::readable_matrix,
    typename IsWritableMatrixView<typename TB::writable_matrix,
void
    >::type>::type
Copy(const TA &A, const TB &B){
    std::cout << "Here" << std::endl;
}

template <class TA, class TB>
    typename IsReadableMatrix<typename TA::readable_matrix,
    typename IsWritableMatrix<typename TB::writable_matrix,
void
    >::type>::type
Copy(const TA &A, TB &B){
    std::cout << "Here2" << std::endl;
}

int main(){
    Matrix<int> M, M2;
    MatrixView<int> V, V2;
    Copy(M, M2);
    Copy(V, V2);
    Copy(M, V);
    Copy(V, M);
}
+1  A: 

Try the following:

template <template <typename> View, typename T>
struct Adapter
{
    // Leave empty to cause errors if used, or you could
    // provide a generic adapter for View<typename T::value_type>
}

// Partial specialization for a given base.
template <typename T>
struct Adapter<MatrixView, T> : MatrixView<typename T::value_type>
{
    const T& t;

    Adapter (const T& t)
      : t(t)
    {}

    void some_virtual()
    {
      // Do stuff
    }
}

template <template <typename> View, typename T>
const View<T>& adapt (const View<T>& v)
{
  return v;
}

template <template <typename> View, typename T>
View<T> adapt (const T& t)
{
  return Adapter<View, T>(t);
}

template <typename T, typename U>
foo(const MatrixViewBase<T> &Mview, const MatrixViewBase<U> &Mview2);

template <typename T, typename U>
foo (const T& t, const U& u)
{
  return foo(adapt<MatrixViewBase>(t), adapt<MatrixViewBase>(u));
}

I put const in everywhere I could; you don't have to use it in every situation if it's not appropriate. You can use specializations of Adapter for a given T to further tailor the behavior.

Interestingly enough, this is the first time I've ever recommended template template parameters.

coppro
There are several syntax issues in the code you gave (the template parameter lists are messed up). Aside from that, the code still does not solve the problem, because I cannot write foo(A) where A is derived from MatrixBase, and foo takes a non-const ref.
Victor Liu
@Victor Liu: No, the template parameter lists are correct. That's a template template parameter, which is a template parameter that is another uninstantiated template. I suppose I wasn't clear after I changed my example around to put the machinery into Adapter; I'll fix that up. Also, I already fixed the non-const reference issue, that was an oversight.
coppro
@coppro, i think he means those "class" keywords are missing :)
Johannes Schaub - litb
+1  A: 

Do you need you base classes to be templates? Can you inject even "more base" nontemplate class? If yes, you can do something like that:

struct Base_A{
    virtual void interface_func() const = 0;
};

template <class T>
struct Derived_A : public Base_A{
    typedef T value_type;
    void interface_func() const{}
};

struct Base_B{
    virtual void interface_func() const = 0;
};

template <class T>
struct Derived_B : public Base_B{
    typedef T value_type;
    void interface_func() const{}
};

struct Adapter
{
    const Base_A* pA;
    const Base_B* pB;

    Adapter(const Base_B &inst_B) : pB(&inst_B), pA(0){}
    Adapter(const Base_A &inst_A) : pA(&inst_A), pB(0){}
    void interface_func() const
    {
     if( 0 != pA )
      pA->interface_func();
     else if( 0 != pB )
      pB->interface_func();
    }
};


void f(const Adapter &arg)
{
    arg.interface_func(); // will call proper interface_func
}


int main()
{
    Derived_A<int> A;
    Derived_B<int> B;
    f(A);
    f(B);
}
BostonLogan
This solution doesn't scale. Imagine if there were 15 base classes; Adapter would be difficult to maintain and a waste of space (as it would store 14 unused pointers)
coppro
A: 

I have not looked into the details of a templated solution, but while you are at it, you can check the boost preprocessor library to help you in defining the 2^N variations of the template. This won't be nice on compile time, but it will be better than manually creating the variations.

David Rodríguez - dribeas
I have almost no experience using Boost. Could you perhaps provide a concrete example?
Victor Liu
I have used boost for a couple of years, but never the preprocessor lib. I would have to dig into tutorials/examples and the 'C++ Template Metaprograming' book to give you a clear example. On the other hand, some other boost libs are implemented using the boost PP lib, so that might serve as examples.
David Rodríguez - dribeas
A: 

I had to do this a while ago and the easiest (to maintain) solution I came up with was to have only one class (Matrix) that knows if it owns it's own data or is a view of someone else's data. E.g.,

Matrix A(n,m);  //Creates a new matrix of size (n,m)
Matrix B=View(A);  //Creates a matrix that's a view into A
Matrix C=View(pData,n,m); //Creates a matrix thats a view of data in a pointer that someone else owns

Foo(A);
Foo(B);
Foo(C);

It makes writing the Matrix class a little more difficult, but then later anyone who uses a Matrix, just uses it as a matrix, they don't need to care if it's a view or not.

Edited to add:

Why not just let BLAS manage the transpose for you? In the end, any of the BLAS functions are just going to want a pointer to a contiguous chunk of memory. So as long as your Matrix class knows how to get to that memory you're good. If the Matrix class has it's own isTransposed flag inside then you can do something like this:

Matrix A(n,m);
Matrix B=TransposedView(pData,m,n);
Matrix C=A*B;

Matrix& operator*(const Matrix& lhs, const Matrix& rhs)
{
  Matrix result(lhs.Rows(),rhs.Cols()); //or did i get that backwards :)
  char TransL=lhs.IsTransposed()?'T':'N';
  char TransR=rhs.IsTransposed()?'T':'N';
  _dgemm(TransL, TransR, ..., lhs.Data(), ..., rhs.Data(), ..., result.Data());
  return result
}
miked
Victor Liu
see my edit for a solution to your BLAS problem. I've actually solved this problem before, unfortunately what I did is proprietary so I can't just send you the code.
miked