niedziela, 28 sierpnia 2011

Class method as template parameter

I had an interesting problem that can be solved using a little bit tricky C++ code. Imagine a singleton that holds some pointer to a class that has many methods with identical params, but different purposes. Now, imagine you're creating some wrapper for this class in some other class (the first one was a ID3D11Device in my case, so I didn't modify it) and your code is also identical, despite this single method call. You can obviously copy paste this problem, creating n-times more lines of code. But this is an ugly solution, so below I paste the nice one. Class A is the ID3D11Device, class B is my wrapper. In my case fun1 was enough (i knew A type while implementing this), but because i find this useful - i paste code for case with unknown A type. Of course in practice, fun1 and fun2 would be big functions with this single call embedded somewhere deep.

struct A {
void a(int param) { std::cout << "a " << param << std::endl; }
void b(int param) { std::cout << "b " << param << std::endl; }
};


struct B {
template< void (A::*F)(int) >
void fun1() {
A* a = new A; // obtain A, this may be singleton or anything else
(a->*F)(1);
delete a;
}


template< typename T, void (T::*F)(int) >
void fun2(T* t) {
(t->*F)(2);
}
};


int main() {
B b;
b.fun1< &A::a >();
b.fun1< &A::b >();


A a;
b.fun2< A,&A::a >(&a);
b.fun2< A, &A::b >(&a);


return 0;
}



The only aspect that may need some explanation at the moment is how to pass ID3D11Device method marked with STDMETHODCALLTYPE as template parameter. Notice, that STDMETHODCALLTYPE is just a macro for __stdcall. We can pass this additional keyword using syntax like:


template< HRESULT (__stdcall ID3D11Device::*CreateShader)(const void*, SIZE_T, ID3D11ClassLinkage*, ID3D11VertexShader**) >
bool load_(const char* filename) {
...
}

Ok, now for the real example - the code will be a little bit more tricky, because we have to use derived template param as param for functions used as template params, blah, compliacted - look at the code :)

template< typename ShaderType >
class Shader {
protected:
ShaderType* shader;
public:
Shader() {
shader = NULL;
}


virtual ~Shader() {
if(shader) {
shader->Release();
}
}


template< HRESULT (__stdcall ID3D11Device::*CreateShader)(const void*, SIZE_T, ID3D11ClassLinkage*, ShaderType**) >
bool load_(const char* filename, const char* entryPoint, const char* shaderModel) {
...
}


template< void (__stdcall ID3D11DeviceContext::*SetShader)(ShaderType*, ID3D11ClassInstance* const*, UINT) >
void bind_() {
...
}
};


class VertexShader : public Shader< ID3D11VertexShader > {
public:
__forceinline bool load(const char* filename) {
return load_< &ID3D11Device::CreateVertexShader >(filename, "mainVS", "vs_4_0");
}


__forceinline void bind() {
bind_< &ID3D11DeviceContext::VSSetShader >();
}
};


class PixelShader : public Shader< ID3D11PixelShader > {
...
};


class GeometryShader : public Shader< ID3D11GeometryShader > {
...
};


class DomainShader : public Shader< ID3D11DomainShader > {
...
};


class HullShader : public Shader< ID3D11HullShader > {
...
};


class ComputeShader : public Shader< ID3D11ComputeShader > {
...
};


Of course you may decide to make load and bind virtuals, and to pack them into some kind of manager - this is just a design decision, whether you want to automate use of those classes. In such case design would get a little bit more complicated. My load is virtual, bind isn't. Additionally, VertexShader is strongly coupled with VertexLayout (ID3D11InputLayout), which is created while loading, so my structure looks something like this:


Brak komentarzy:

Prześlij komentarz