0

In a class header file Texture.h I declare a static const int.

static const int MAX_TEXTURE_SLOTS;

In Texture.cpp I define the variable as 0.

const int Texture::MAX_TEXTURE_SLOTS = 0;

Now in Window.cpp class's constructor I attempt to assign to the variable using an out parameter, however this obviously does not compile as &Texture::MAX_TEXTURE_SLOTS points to a const int* and not an int* .

glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &Texture::MAX_TEXTURE_SLOTS);

I have tried using const_cast, but am greeted with a segmentation fault on runtime.

glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, const_cast<int*>(&Texture::MAX_TEXTURE_SLOTS));

I have also tried directly casting to an int * but once again, seg fault.

glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, (int*)&Texture::MAX_TEXTURE_SLOTS);

Many thanks.

  • 6
    why is it const in the first place when it isn't const? – Raildex Apr 05 '22 at 16:46
  • 2
    Why declare it as `const` if you intend to change it? – Adrian Mole Apr 05 '22 at 16:46
  • *"I attempt to assign to the variable"* - which you cannot do to const vars; you can only *initialize* them (and, in fact, they *must* be initialized in one form or another). I think your [rubber duck](https://en.wikipedia.org/wiki/Rubber_duck_debugging) deserves an explanation here. – WhozCraig Apr 05 '22 at 16:48
  • You cannot change a constant. Just remove `const` – KoVadim Apr 05 '22 at 16:48
  • Think of it this way: The compiler for example sees "Hey, It's const and it is 0. This for-loop checks against it. Since it's const and 0, I can omit this for loop :)" This is obviously simplified, but that's what the compiler expects. It is const and 0. Changing it at runtime via obscure casts breaks your program. – Raildex Apr 05 '22 at 16:48
  • Why is this code in the `Window` class and not the `Renderer` class where it makes more sense? – Casey Apr 05 '22 at 16:55

3 Answers3

1

Provided that glGetIntegerv doesn't depend on other gl* functions being called before, you may use an immediately-invoked lambda:

// Texture.cpp

const int Texture::MAX_TEXTURE_SLOTS = []
{
    int maxTextureSlots{0}:
    glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureSlots);
    return maxTextureSlots;
}();
paolo
  • 2,345
  • 1
  • 3
  • 17
  • Unfortunately `glGetInteger` does indeed depend on functions called before, namely the creation and binding of an OpenGL context. Furthermore the value retrieved may change depending on which OpenGL context has been bound. – datenwolf Apr 05 '22 at 16:50
  • @datenwolf Valid point, however, from a language standpoint, this is as valid as `const int x = rand();`. It's fine to treat the variable in which the result of a function is placed as const for any scope you so choose, regardless of of whether or not that function will ever return a different value. – Wyck Apr 05 '22 at 16:53
  • @Wyck: The problem is, that the value may actually change. `GLint a, b; …glMakeCurrent(glrc_a, …); glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &a); …glMakeCurrent(glrc_b, …); glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &b);` → a and b may have different values. – datenwolf Apr 05 '22 at 16:55
  • @datenwolf I totally agree with you. The folly is likely that it appears to be a static member of a class and therefore will receive its value as part of static initialization and the gl state is not going to be valid at static initialization time, if the implementation were a single call to `glGetIntegerv` alone. Nevertheless, the implementation of the lambda could be expanded to ensure that state *is* correct, so the general approach of how to initialize a const with a function result is right - the implementation shown is insufficient for static initialization due to missing OpenGL init. – Wyck Apr 05 '22 at 17:07
1

EDIT 2: So since you're trying to abstract OpenGL contexts, you'll have to let go of the "traditional" constructor/destructor idioms. And just for your information (unrelated to this question): OpenGL contexts are not tied to windows! As long as a set of windows and OpenGL contexts are compatible with each other, you may mix and match any way you like. But I digress.

The standard idiom to deal with a situation like yours is to use preinitializing factory functions. Like this:

class MyOpenGLContextWrapper {
public:
    // Yes, shared_ptr; using a unique_ptr here for objects that are kind
    // of a nexus for other things -- like an OpenGL context -- just creates
    // a lot of pain and misery. Trust me, I know what I'm talkink about.
    typedef std::shared_ptr<MyOpenGLContextWrapper> ptr;

    struct constdata {
        NativeGLContextType context;
        // ...
        GLint max_texture_image_units;
        // ...
    };

    static ptr create();

protected:
    MyOpenGLContextWrapper(constdata const &cdata) : c(cdata) {};
    virtual ~MyOpenGLContextWrapper();
    constdata const c;
}

MyOpenGLContextWrapper::ptr MyOpenGLContextWrapper::create()
{
    struct object : public MyOpenGLContextWrapper {
        object(MyOpenGLContextWrapper::constdata const &cdata) : MyOpenGLContextWrapper(cdata) {}
        ~object(){}
    };

    MyOpenGLContextWrapper::constdata cdata = {};

    // of course this should all also do error checking and failure rollbacks
    cdata.context = create_opengl_context();
    bind_opengl_context(cdata.context);
    // ...
    glGetInteger(GL_MAX_TEXTURE_IMAGE_UNITS, &cdata.max_texture_image_units);

    return std::make_shared<object>(cdata);
}

EDIT: I just saw that you intend to use this to hold on to a OpenGL limit. In that case you can't do this on a global scope anyway, since those values depend on the OpenGL context in use. A process may have several OpenGL contexts, each with different limits.


On most computer systems you'll encounter these days, variables declared const in global scope will be placed in memory that has been marked as read only. You literally can't assign to such a variable.

The usual approach to implement global scope runtime constants is by means of query functions that will return from an internal or otherwise concealed or protected value. Like

// header.h
int runtime_constant();
#define RUNTIME_CONSTANT runtime_constant()

// implementation.c / .cpp
int runtime_constant_query(){
    static int x = 0;
    // NOTE: This is not thread safe!
    if( !x ){ x = determine_value(); }
    return x;
}

You can then fetch the value by calling that function.

datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • 1
    The if statement isn't needed here, and if `determine_value` returns `0` you'll always be calling that. Instead, you can just use `static int x = determine_value(); return x;` – NathanOliver Apr 05 '22 at 16:55
  • Using static initialization leads to incredibly hard to debug issues on large scale project. There's no facility to specify the initialization order. The local static version, at least, fixes the initialization order – Jeffrey Apr 05 '22 at 16:57
  • @Jeffrey Indeed. The only question I have is why `x` is not initialized with `determine_value()` directly. – Ted Lyngmo Apr 05 '22 at 16:59
  • 1
    Thanks for the explanation on the memory side, really helpful. Unfortunately I can't upvote you cause my rep is too low but thanks anyway. I'll probably just get rid of `const` then. – Freddy Cansick Apr 05 '22 at 16:59
  • @TedLyngmo: It's initialized upon runtime using an conditional, because the value might actually not be available until late in process initialization; it might become available only right before `main` is entered, or even later. Heck, it might even go as far as doing a full blown library initialization in the case x hasn't been initialized yet. – datenwolf Apr 05 '22 at 17:14
  • @FreddyCansick: See my "EDIT2" using the factory function idiom, in which the constant data of a class instance is initialized prior to construction and then the constructor called with a reference to the constant data to be used for the object. – datenwolf Apr 05 '22 at 17:22
  • (I think you have a typo: `runtime_constant_query()` vs. `runtime_constant()`) – Ted Lyngmo Apr 05 '22 at 17:25
  • @datenwolf Hmm, I think I let my C++ glasses color my comments... `static int x = determine_value();` don't work at all in C, does it? :-) – Ted Lyngmo Apr 05 '22 at 17:29
  • Fwiw, I think a thread safe version could look like [this](https://godbolt.org/z/e3PEcY4Gf) – Ted Lyngmo Apr 05 '22 at 17:44
0

You don't.

You can't assign to a const outside of its definition. Also, using a const variable where the const has been const_casted away is UB. This also means you can't directly initialize a const variable with an output parameter. For trivial types, just output to another variable and make a const copy if you so wish.

If you were the author of the function you're calling, you would do well not to use out parameters, and then you could assign to const variables directly, perhaps using structured bindings if you want to name multiple of the outputs at a time. But here, you're not.

Julien BERNARD
  • 653
  • 6
  • 17