0

I am designing an internal API that deals with images. As part of this, I hit this use case where:

  • I specify an image format as a single value.
  • An image format implies a collection of settings (values to pass to OpenGL, fragment shader code, texel bit size, …).

In essence, a trait would be perfect… except the image format is not known at compile time.

I am looking for a good way to express this in C++ (C++17 allowable).

So far, here is an attempt that works but feels very awkward:

// pixel_format_def is a structure with static constexpr members

using R8 = detail::pixel_format_def<GL_R8, GL_RED, GL_UNSIGNED_BYTE>;
using RG8 = detail::pixel_format_def<GL_RG8, GL_RG, GL_UNSIGNED_BYTE>;
// ... many others
using PixelFormat = std::variant<R8, RG8>;

The actual pixel_format_def has more fields. It is then used like this:

void someFunc(PixelFormat format /*, other args */)
{
    std::visit([&](auto tag) {
        using Format = decltype(tag);
        someFunc(Format::format, Format::type/* other args */);
    }, format);
}

Basically this does tag dispatching, with std::variant holding a tag.

It fulfills my requirements:

  • If the format is known at compile time, the compiler inlines the call and removes the std::visit. For instance:

      someFunc(RG8());
    

    will be inlined (at least by clang and gcc) directly to

      someFunc(GL_RG, GL_UNSIGNED_BYTE);
    
  • Yet it works if the format is only known at runtime. In which case std::visit will pick the correct values Without passing around a big structure.

It feels really awkward though, almost a mis-use of std::variant. And I am not sure it would pass code review. Any better idiom?

9
  • What exactly is wrong with using a map or similar object? Is there some memory overhead issue that you have? Commented Dec 10, 2019 at 20:56
  • It cannot be created at compile time, which means the compiler cannot optimize the case where the type is known at compile time (plus all the usual caveats of initializing global variables during program startup)
    – spectras
    Commented Dec 10, 2019 at 21:04
  • "which means the compiler cannot optimize the case where the type is known at compile time" What does that mean in this context? Because you don't really have types; you're abusing the type system as a way of storing collections of values. So what optimization exactly are you looking for here? Commented Dec 10, 2019 at 21:06
  • Sorry I meant image format. Whenever the image format is known at compile time, eg if I call someFunc(RG8()), the compiler inlines it as someFunc(GL_RG, GL_UNSIGNED_BYTE). Neat and efficient, no need to go looking for the values in some runtime structure. — I just added that precision to the question btw.
    – spectras
    Commented Dec 10, 2019 at 21:09
  • 1
    Is "looking for the values in some runtime structure" really a performance bottleneck in your application, or are we talking about a premature optimization here? And if it is a bottleneck, have you tested it in real-world performance? Because in order for someFunc to be "inlined", it'd have to be a template, right? Which means you have to instantiate someFunc for every image format type. Which means you will have a lot of these lying around, so if you're calling a bunch of different someFunc<type> instantiations, you'll miss a lot on the instruction cache. Commented Dec 10, 2019 at 21:13

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.