There are lots of “features” in C++ that I try to avoid in my APIs until I know I need them. Even virtual functions can often be overkill. However, one thing C++ can do very well is make APIs (especially those featuring objects…) very simple to understand and quick to control.
Overmind, your example still highlights the problem with the overly terse C APIs.
In this case, the first approach I’d try in C++ is see if the (enum,int,int) bits can be wrapped in their own typed object for clarity. In the case of glTemplates, if these are client-side objects, it might be okay to actually build a GLTemplate class with actual getters and setters specialized to the attribute and its various parameters. But trying to avoid any appearance of big [Microsoft-like] versioned gettr/settr structs, I’d start with a lot of little ones, where more can be added down the road without changing any names or offsets. Assuming some GL.h header base class like:
template <int GLID, typename T1, typename T2>
class glAttrib
{
protected:
inline glAttrib();
public:
inline void Set(const T1& t1, const T2& t2);
inline void Get( T1& t1, T2& t2) const;
inline void Apply(glTemplate*) const;
// type-specialized implementations of Apply can call the proper glTemplateAttibXX_X function under the hood
...
private:
// storage
// T x;
// T y;
// a variable number of params could be handled in
// several ways, one of which is to push these
// into the more derived classes.
};
// here go the Enum replacements:
struct glSomeAttrib : public glAttrib<GL_SOME_TOKEN, int, int> { glSomeAttrib(int x, int y) { Set(x,y); } };
struct glOtherAttrib : public glAttrib<GL_OTHER_TOKEN, int, int> { glOtherAttrib(int x, int y) { Set(x,y); } };
// And perhaps a few more wrappers for convenience:
template <typename A>
void glTemplateAttrib(glTemplate *ptr, const A& attrib) { A.Apply(ptr); }
class glTemplate
{
GL_TEMPLATE_HANDLE handle;
// omitting lots...
public:
template <typename A> inline void SetAttrib(const A& attrib) { A.Apply(this); }
};
The main goal is to have the compiler find mismatches between enums and function parameters and avoid more runtime errors while adding more clarity. But it might also be nice for us to be able to use these classes to store attributes or whatever object-ish stuff we’re talking about in our programs, for faster/cleaner dispatch – not the whole driver-side objects themselves, which we shouldn’t ever know or rely on, but the attribute tuples themselves should be safe.
So, in this case, I’d probably subclass this GLAttrib base for each specific enum type, specializing the parameter type(s) to fit each precisely and trying to add semantic information in the names, or in additional fields as needed.
In the case of an int-indexed attribute vs. a pair of ints as an attrib, those would be two different attribute types (classes) and therefore the names would be clear and different.
Anyway, all that header “goodness” aside, the end user would see and use a much simpler API:
template->SetAttrib(glSomeAttrib(x,y));
including the option of keeping these objects around:
glSomeAttrib A(x,y);
template->SetAttrib(A);
or slightly more C-like:
glTemplateAttrib(template,glSomeAttrib(x,y));
“GLSomeAttrib” might be better to connote data structures vs. function calls than lower case. Namespaces are even better. The glSomeAttrib could potentially carry the suffixes for extra clarity, or even be templatized itself for more options. [The downside in that case is the compiler error when trying to use an unsupported template combination is more cryptic than it should really be. See BOOST_STATIC_ASSERT for some ideas on how to provide better custom compile-time errors.]
All of this can easily wrap the messier (IMO) C API without needing a different DLL. Show me any case it can’t handle and I’ll make a quick adjustment.
And also, btw, this is the leasst intrusive of many approaches. If we took a “mixin” approach, we could even do away with setting up templates in piece-meal fashion. A custom client-side class could be composed of the elements you wanted. This class could be a runtime object you could use. But it could have enough meta information to describe itself to the driver, as GL-templates seem to do. But that’s more of an under-the-hood change, so I’m not actively suggesting that, though I use it myself for some really flexible vertex buffer wrapper classes.