• Advertisement
Sign in to follow this  

[C++] Compile time error in default case

This topic is 3458 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I need to map certain OpenGL enums to indices in range 0..N-1 where N is the number of enums. This should happen in compile time and in such a way that unsupported enums generate an compilation error. This would be used in OpenGL state cache/stack 'library'. Partial solution for mapping function might be:
inline EnableState GetState(GLenum glenum) {
  switch(glenum) {
  case GL_ALPHA_TEST: 	  return ALPHA_TEST;    // 0
  ..
  case GL_VERTEX_ARRAY:   return VERTEX_ARRAY;  // N-1
  default: 		  <GENERATE_COMPILE_TIME_ERROR>
  }
}
this is called by other inline functions that enable/disable/push/pop opengl states. For example Enable function:
inline void Enable(GLenum glenum) {
  const EnableState state = GetState(glenum);
  if(!*EnableStackPtrs[state]) {
    glEnable(EnableEnums[state]);
    *EnableStackPtrs[state] = true;
  }
}
This works and all inlines collapse into tight code so that's good so far. Problem is I haven't been able to work out satisfactory solution to that second requirement: error generation at compile-time when 'wrong' GLenum is used. Either A) error happens runtime or B) error happens ALWAYS at compile-time. I just can't get it to work _only_ when that invalid enum is used. Part of the problem is that constant value given as argument to function is not an constant expression anymore inside the function. If it was I could fex. use template specialization instead to do the mapping and errors would be automatically caught:
template<GLenum> struct MapEnum {};
template<> struct MapEnum<GL_ALPHA_TEST> { enum { value = DEPTH_TEST }; };
..
template<> struct MapEnum<GL_VERTEX_ARRAY> { enum { value = VERTEX_ARRAY }; };

inline void Enable(GLenum glenum) {
  const EnableState state = MapEnum<glenum>::value;
  ..
}

I could give up and just throw error at runtime, but that feels somehow wrong. I hope there might be an easy solution and it's just my ignorance of this cursed run/compile time boundary thing that's confusing me. I would appreciate any comments or ideas here.

Share this post


Link to post
Share on other sites
Advertisement
It's easy: if you wish to check the value at compile-time, you must know the value at compile time. The entire point of a function parameter is that its value is not known at compile time. Therefore, it's impossible to check if the value of a function parameter is correct at compile time.

If you wish to guarantee that only a certain set of enumeration values will be used, use an enumeration with exactly that many values everywhere in your code, and you'll never get other values because the type system will take care of you.

Share this post


Link to post
Share on other sites
Thanks for reply

I was somehow hoping there might be a way to bypass those C++ constant expression requirements when functions are inline. But guess not.

I actually have the system setup as you suggest and it works well enough. It's just that rest of the code uses GLenums as arguments (and other GLtypes) so it would have been nice to have unified appearance in the interface and still have errors reported when trying to compile. But it works so can't really complain...

Share this post


Link to post
Share on other sites
Quick guess: you could try creating templates with an integer non-type parameter, specializing them for each valid case, and giving a compile-time error in the default case.

Share this post


Link to post
Share on other sites
Quote:
Original post by ToohrVyk
If you wish to guarantee that only a certain set of enumeration values will be used, use an enumeration with exactly that many values everywhere in your code, and you'll never get other values because the type system will take care of you.


Almost. You forgot that you don't have to initialize enums :(

MyEnum i;
foo(i);

Share this post


Link to post
Share on other sites
It is also possible to assign any value to an enum variable with a cast.

As already said, a function receiving an incorrect argument is a run-time error which you cannot detect at compile-time. You might consider throwing an exception (some other run-time error handling mechanism) in the default case.

Share this post


Link to post
Share on other sites
Quote:
Original post by MadKeithV
Quick guess: you could try creating templates with an integer non-type parameter, specializing them for each valid case, and giving a compile-time error in the default case.


Something like this ?

<in client code>
lib::Enable(lib::Map<GL_DEPTH_TEST>::index);

i guess that would work, but kind of defeats the whole purpose of making things
appear more simple to user :/

If I put that template inside Enable() function

inline void Enable(GLenum glenum) {
const int i = Map<glenum>::index;
..
}


my compiler gives:

test.cpp:55: error: ‘glenum’ cannot appear in a constant-expression
test.cpp:55: error: template argument 1 is invalid

I'm not sure how to make it work that way. Maybe it's impossible at present. Sure would be nice feature to have.

Share this post


Link to post
Share on other sites
You can't do that, that is a runtime variable. You can however use tags. That function would become:


struct gl_vertex_array_tag {
static GLenum value = GL_VERTEX_ARRAY;
};

template <typename T>
inline void Enable() {
const int i = T::value;
..
}

// Calling code
Enable<gl_vertex_array_tag>();

Share this post


Link to post
Share on other sites
Quote:
Original post by jkv
Quote:
Original post by MadKeithV
Quick guess: you could try creating templates with an integer non-type parameter, specializing them for each valid case, and giving a compile-time error in the default case.


Something like this ?

<in client code>
lib::Enable(lib::Map<GL_DEPTH_TEST>::index);

i guess that would work, but kind of defeats the whole purpose of making things
appear more simple to user :/

Why not something like

lib::Enable<GL_DEPTH_TEST>();


Quote:
If I put that template inside Enable() function

*** Source Snippet Removed ***

my compiler gives:

test.cpp:55: error: ‘glenum’ cannot appear in a constant-expression
test.cpp:55: error: template argument 1 is invalid

Yep, as already said, the function argument is a runtime expression. While compiling the function, it is not known which arguments it is called with. (And even if the compiler can figure this out through some global optimizations, the language doesn't permit it to treat the argument as a compile-time expression)
You've made a function which takes an argument at runtime. So it's impossible to stuff that runtime argument into a compile-time template.

Share this post


Link to post
Share on other sites
Quote:
Original post by jkv
I need to map certain OpenGL enums to indices in range 0..N-1 where N is the number of enums.


Why can't you just use the actual enum values?

Are you trying to combine a few OpenGL enums into some kind of super-enum? If so, what on earth problem do you think it will solve to do so?

Quote:
This should happen in compile time


You're passing a parameter to the function, which is what you're switching on. The parameter's value isn't necessarily known at compile time, so how can you expect the compiler to make a determination at compile time?

Quote:

Part of the problem is that constant value given as argument to function is not an constant expression anymore inside the function. If it was I could fex. use template specialization instead to do the mapping and errors would be automatically caught:


Did you know that you can template on (integral) values, not only types?

Quote:
I could give up and just throw error at runtime, but that feels somehow wrong.


Why?

Share this post


Link to post
Share on other sites
Quote:
Why not something like

lib::Enable<GL_DEPTH_TEST>();


Yeah, that works. But it's no better solution to the 'problem' of having different appearance to other functions. I would as well use what I currently have, that is simple enumerators as described above.

Quote:
Why can't you just use the actual enum values?


I didn't mention that in the first post, but that is what I had at first. Now what I tried, for aesthetic reasons, was to unify the function signature styles. Fex. I have a function StencilFunc(GLenum, GLint, GLuint) that sets OpenGL Stencil Function states. Because these accept enums like GL_LEQUAL, I thought there was inconsistency here so I tried to fix it.

I handle OpenGL enable states like GL_DEPTH_TEST, GL_LIGHTING all in one set of functions and the ones like glStencilFunc each in their own set of functions. So I could either change the enable states to support directly GL_* or change functionstates to support my own enumerators... I figured it might be easier to do the first.

Quote:
Quote:
I could give up and just throw error at runtime, but that feels somehow wrong.
Why?

Well I do have already compile time assurance for correct values with the enums (well at least almost, as above posters have mentioned) so having to resort to run time checks feels like a step back :)

But even so, I have decided to accept that step and did change the code to use GLenums only, and letting errors go unnoticed until run time.

Share this post


Link to post
Share on other sites
Quote:
Original post by jkv
Quote:
Why not something like

lib::Enable<GL_DEPTH_TEST>();


Yeah, that works. But it's no better solution to the 'problem' of having different appearance to other functions. I would as well use what I currently have, that is simple enumerators as described above.

Quote:
Why can't you just use the actual enum values?


I didn't mention that in the first post, but that is what I had at first. Now what I tried, for aesthetic reasons, was to unify the function signature styles. Fex. I have a function StencilFunc(GLenum, GLint, GLuint) that sets OpenGL Stencil Function states. Because these accept enums like GL_LEQUAL, I thought there was inconsistency here so I tried to fix it.


You don't actually want to unify anything here. What you really want to do is preserve the information about which enumeration type is being used, instead of letting it be erased by the 'GLenum' typedef (which I'm assuming is just int or something like that).

Make your own enums which include the appropriate values, and pass values of the appropriate enumeration type:


enum GL_COMPARISON { GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_LESS, GL_GREATER, GL_NOTEQUAL }; // or something like that

void StencilFunc(GL_COMPARISON, GLint, GLint);
void enableAndLog(GL_FEATURE_ID, ostream&);


That kind of thing.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement