My goal for this code is to constrain a function parameter's passed value to a select few possibilities, being checked at compile time in C 20. My original broken first attempt looked something like this:
template<GLenum shader_type>
requires requires
{
shader_type == GL_VERTEX_SHADER ||
shader_type == GL_FRAGMENT_SHADER ||
shader_type == GL_COMPUTE_SHADER;
}
GLuint create_shader(std::filesystem::path const& shader_path, GLenum shader_type)
This would not work, and I'm not going to argue with the compiler, but I'm including it here to help illustrate my aims for this declaration. My spec:
- Constrain a single passed
GLenum
to a set of three currently supported types. Preferably in an ad-hoc way, e.g. not requiring much boilerplate. - For a user, be able to call this function like so:
create_shader("my/path", GL_FRAGMENT_SHADER)
. No templates in sight.
The main issues with this code were:
declaration of 'shader_type' shadows a template parameter
. I can see this is because of the specification ofGLenum shader_type
in both the template parameters and function arguments, but I was hoping that somehow have the compiler might infer the type from function args.shader_type == GL_VERTEX_SHADER || shader_type == GL_FRAGMENT_SHADER || shader_type == GL_COMPUTE_SHADER
is not doing it's job at all. I could passGL_GEOMETRY_SHADER
in with no compile-time issue. (I wish I could say the same for run-time)
After looking at this Question I came up with this next:
template<GLenum shader_type>
requires requires
{
requires shader_type == GL_VERTEX_SHADER ||
shader_type == GL_FRAGMENT_SHADER ||
shader_type == GL_COMPUTE_SHADER;
}
GLuint create_shader(std::filesystem::path const& shader_path)
This seems to work as I desire when it comes to constraining the passed value to those values, however, it does require shader_type
being passed in as a template parameter. This is fine really, but it leads me to my questions:
- Is there a way to do as I've done in terms of constraints but without having the user pass in
shader_type
as a template parameter explicitly, instead preferring a function argument? - Is there a way of writing this declaration with fewer
requires
? I suspect not, but it would be nice if I can cut one out. - Generally, does anyone know any alternatives which are "better" on the whole. Perhaps referring to my spec at the top of this question for what "better" might be.
Appendix:
- I'm aware of
constexpr
and that I could probably do the check inside the function, but doing it through constraints and concepts is also a bit of exploration for me, as a learner. - In case people are confused by
GLenum
or any of the other non-standard code:
typedef unsigned int GLenum;
typedef unsigned int GLuint;
#define GL_FRAGMENT_SHADER 0x8B30
#define GL_VERTEX_SHADER 0x8B31
#define GL_COMPUTE_SHADER 0x91B9
CodePudding user response:
If you wanted to constrain a template parameter, a single requires
is enough:
template <GLenum shader_type>
requires(shader_type == GL_VERTEX_SHADER || shader_type == GL_FRAGMENT_SHADER)
void foo() {}
A function parameter can be constrained like this, at the cost of requiring it to be a compile-time constant:
struct ShaderType
{
GLenum value;
consteval ShaderType(GLenum value) : value(value)
{
if (shader_type != GL_VERTEX_SHADER && shader_type != GL_FRAGMENT_SHADER)
throw "Invalid value!";
// Normally it's not a good idea to throw pointers,
// but here it just used to stop the compilation.
}
};
void foo(ShaderType type) {}
CodePudding user response:
You cannot constrain a runtime value (like a function parameter) with a compile-time construct (like a requires
clause). What you want is not a compile-time constraint; it's a runtime contract.
C does not as of yet have such a feature. The best you can do at present is use a proper enumeration (or typed static constexpr
variables) that only permits certain values.