[C++] Compile time error in default case

Started by
10 comments, last by Zahlman 15 years, 8 months ago
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.
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.
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...
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.
It's only funny 'till someone gets hurt.And then it's just hilarious.Unless it's you.
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);

--Michael Fawcett
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.
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.
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 codeEnable<gl_vertex_array_tag>();
--Michael Fawcett
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.
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?

This topic is closed to new replies.

Advertisement