2

I'm currently working on a WebGL-like OpenGL wrapper in , which involves verifying that arguments are actually valid. The problem, more or less, is that OpenGL has a ton, and I'm not exaggerating here, of valid arguments for certain functions. (Image of a naive conditional (possible internalformat constants for glTexImage2D, if you're interested. I literally wrote a JS table parser/conditional maker for a few of those) to make my point. That's an assert...)

The logic of such checks is simple enough:

bool check(const unsigned int var)
{
    return var == CONSTANT_1 || var == CONSTANT_2 || var == CONSTANT_...;
}

But, as per the above example, this can stretch for up to 84 possible constants, which is... yeah. And I'm fully aware that a macro wouldn't cut this down by an absurd amount, but it would still make a difference, and I feel like it would be cleaner too. Not to mention the possibility of other macros of a similar fashion with different operators.

So my idea was to use a macro. After all, this check would be trivial to do at runtime, with a std::initializer_list``, std::vector, std::array` or some other container, but since all of these constants are known at compile time, I feel as though this is unnecessary.

Since the number of possible constants is variable, I see no way of not using a variadic macro. Yet I don't know how I would accomplish this. One possible way I thought of doing this is basically overloading a macro based on the number of arguments (akin to this), but this seems unnecessarily complex.

In essence, I'm looking for a macro that would fulfill the following:

MACRO(var, CONSTANT_1, CONSTANT_2, CONSTANT_3)

expands to

var == CONSTANT_1 || var == CONSTANT_2 || var == CONSTANT_3

with any number of constants (important!, and the hard part).

JeJo
  • 30,635
  • 6
  • 49
  • 88
TARN4T1ON
  • 522
  • 2
  • 12
  • 1
    drop macros use templates. – Marek R Oct 20 '21 at 06:52
  • @康桓瑋 Wow, how I didn't think of that, I don't know, but that is perfect. – TARN4T1ON Oct 20 '21 at 06:56
  • 2
    I can't see anything meaningful in the picture. If I zoom to 500% there's just a blurry picture of something that may be code. – Ted Lyngmo Oct 20 '21 at 06:58
  • @TedLyngmo That's the point. There's just so many constants. – TARN4T1ON Oct 20 '21 at 07:02
  • 1
    technically openGL does that by itself at runtime...but if you want a compile time check, you shouldn't use a macro. And if constant is valid it doesn't mean it is a valid argument for current implementation in all cases, it depends on how context and standard determines that – Swift - Friday Pie Oct 20 '21 at 07:05
  • @Swift-FridayPie I know OpenGL does that, but it's own error handling sucks, to be frank, and is sometimes less than helpful. As for what's actually valid, that's handled completely differently, this is just the first stage of checking essentially, whether or not an argument can even be valid in the first place. – TARN4T1ON Oct 20 '21 at 07:18
  • 1
    @TARN4T1ON so essentially you want to reimplement whole state-machine that would be correct for hardware and version of context you use? that's goes beyond error checking, that's practically emulating. AT this point some kind of wrapper around OpenGL API is preferable – Swift - Friday Pie Oct 20 '21 at 07:22
  • @Swift-FridayPie Heh, not the whole thing, by far. And I'm aware I would never be able to make it perfect. It's really just the parts that have an impact on performance (basically ensuring all the major state changes like contexts, programs, VAOs, VBOs, textures, framebuffers... aren't unnecessary) or have a chance of critical failure (like an invalid context or buffering VBO or texture data, where the data size is validated). This basic constant validation is more of just a "Hey, this isn't even possible" with more detail. It's just a wrapper after all. – TARN4T1ON Oct 20 '21 at 07:37
  • @TARN4T1ON not impossible, but labor consuming and usually appearing as part of pre-existing large subsystem or or result of refactoring such. Rolling it from ground up is quite an a task. I saw implementation which even was supporting things like emulation of glBegin\glEnd or display lists on a context without fixed pipeline. Checking parameters there was partially done, but mostly was made irrelevant. No "user code" had access to the real OpenGL – Swift - Friday Pie Oct 21 '21 at 06:15

3 Answers3

5

You might want to use C++17 fold expression:

template<class... Args>
bool check(const unsigned int var, const Args&... args)
{
  return ((var == args) || ...);
}

Then you can invoke something like this:

check(var, CONSTANT_1, CONSTANT_2, CONSTANT_3);
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
3

You do not need the macros. If you have the constants in a constexpr std::array, you can make use of std::any_of as follows:

#include <algorithm>  // std::any_of

inline static constexpr std::array<unsigned int, 2> arr_of_const{ CONSTANT_1, CONSTANT_2 };

consteval bool check(unsigned int var)
{
    return std::any_of(arr_of_const.cbegin(), arr_of_const.cend(), [var](auto ele) { return var == ele; });
}
JeJo
  • 30,635
  • 6
  • 49
  • 88
2

No need to use macros here, a simple function and array of valid values will do the job:

#include <array>
#include <algorithm>
#include <iostream>

constexpr int CONSTANT_1 = 0;
constexpr int CONSTANT_2 = 1;
constexpr int CONSTANT_3 = 2;
constexpr int CONSTANT_4 = 3;

constexpr std::array<int, 3 > VALID_CONSTANTS = { CONSTANT_1, CONSTANT_2, CONSTANT_3 };

template <size_t N>
constexpr bool check(int value, const std::array<int, N>& values)
{
    return std::find(values.begin(), values.end(), value) != values.end();
}

int main()
{
    std::cout << check(CONSTANT_1, VALID_CONSTANTS) << "\n";
    std::cout << check(CONSTANT_4, VALID_CONSTANTS) << "\n";
}

With a small modification the code is completely evaluated at compile time:

#include <array>
#include <algorithm>
#include <iostream>

constexpr int CONSTANT_1 = 0;
constexpr int CONSTANT_2 = 1;
constexpr int CONSTANT_3 = 2;
constexpr int CONSTANT_4 = 3;

struct VALID_CONSTANTS
{
    static constexpr std::array<int, 3 > values = { CONSTANT_1, CONSTANT_2, CONSTANT_3 };
};

template <typename Values>
constexpr bool check(int value)
{
    return std::find(Values::values.begin(), Values::values.end(), value) != Values::values.end();
}

int main()
{
    std::cout << check<VALID_CONSTANTS>(CONSTANT_1) << "\n";
    std::cout << check<VALID_CONSTANTS>(CONSTANT_4) << "\n";
}
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60