Home > database >  Constrain non-type/value template parameter using requires in an ad-hoc fashion
Constrain non-type/value template parameter using requires in an ad-hoc fashion

Time:02-20

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:

  1. 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.
  2. 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:

  1. declaration of 'shader_type' shadows a template parameter. I can see this is because of the specification of GLenum 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.
  2. 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 pass GL_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:

  1. 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?
  2. 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.
  3. 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:

  1. 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.
  2. 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.

  • Related