3

I have a C application which has a large amount of highly fragmented configuration data and behavior that I would like to consolidate, eg:

// In one file
#define VAR_1 1
#define VAR_2 2
#define VAR_3 3

// In another file
const gNames[] = {
  [VAR_1] = (const u8[]) "1",
  [VAR_2] = (const u8[]) "2",
  [VAR_3] = (const u8[]) "3",
};

// In yet another file
switch (var) {
  case VAR_1:
    // some behavior
    break;

  case VAR_3:
    // some other behavior
    break;

// And so on

I'd like to consolidate this data, eg:

const Behavior gBehaviors[] = {
  [VAR_1] = {
    .name = (const u8[]) "1",
    .handleFooEvent = Var1FooHandler,
    // ...
  },
  // ...

However as far as I can tell the inability to declare anonymous methods in C will make this pretty miserable as I wont be able to keep handlers and constants grouped together. I'm not particularly experienced with C++, but as far as I can tell there are a number of ways in which I can cleanly declare the individual behavior classes I need, however as C++ doesn't seem to have support for designated identifiers and I need to maintain indexing that matches with our existing #define enum I'm not sure how I can actually create a mapping that allows the C code to go from an enum to the appropriate behavior. Is there a reasonable way that I could make this work, either in C, C++, or even some other language/technology?

6
  • 2
    designated initializers are a C++20 feature. Commented Dec 3, 2024 at 8:27
  • 1
    @kzsnyk - not the array form Commented Dec 3, 2024 at 8:28
  • 3
    (const u8[]) isn't valid C. I don't think it is valid C++ either unless some strange operator overloading was done? Commented Dec 3, 2024 at 8:35
  • " the inability to declare anonymous methods in C will make this pretty miserable as I wont be able to keep handlers and constants grouped together" This is a made up problem. Just use function pointers. Commented Dec 3, 2024 at 8:39
  • "C++ doesn't seem to have support for designated identifiers" It should have as per C++20. It's not clear why you need to port the code to C++ however. Commented Dec 3, 2024 at 8:39

2 Answers 2

0

You can do this in the way you ask. C++ does have anonoymous functions They are called lambdas. If you want to have a set of lambdas that are dispatched to based on an integer key you can do this. Whether you should do this is another matter. Standard C++ classes should probably be your first port of call. However with that warning:

https://godbolt.org/z/fTbsGcE96

#include <iostream>
#include <utility>
#include <type_traits>

template <typename... Ts>
struct Overloaded : Ts... {
    using Ts::operator()...;
};


template <int X, typename Callable, typename... Args>
void call(Callable&& callable, Args&&... args) {
    std::forward<Callable>(callable)(std::integral_constant<int, X>{}, std::forward<Args>(args)...);
}


#define VAR_1 1
#define VAR_2 5
#define VAR_3 9

int main() {

    auto fn = Overloaded{
        // Specialization of lambda for each compile-time value
        [](std::integral_constant<int, VAR_1>, int a, int b) {
            std::cout << "a + b = " << (a + b) << "\n";
        },
        [](std::integral_constant<int, VAR_2>, int a, int b) {
            std::cout << "a - b = " << (a - b) << "\n";
        },
        [](std::integral_constant<int, VAR_3>, int a, int b) {
            std::cout << "a * b = " << (a * b) << "\n";
        }
    };

    call<VAR_1>(fn, 5, 3);  
    call<VAR_2>(fn, 5, 3);  
    call<VAR_3>(fn, 5, 3);  
}

If we break this down. Overloaded is a pattern for turning an list of anonymous functions (known as lambdas in c++) into a single function like object that when itself called matches the type of it's argument and dispatches to the matching sub lambda.

You can find more info on how this works here

To allow us to dispatch to different handlers based on integer we can use a generic type called std::integral_constant<type, val> which generates a unique type based on the input value. We simply add a tag parameter as the first parameter to each lambda with the integral constant type we wish to use to use to mark that lambda.

The call function simply wraps this up in a convenient interface.

Sign up to request clarification or add additional context in comments.

4 Comments

This seems promising, although at a glance it doesn't look like that would then be accessible from C code. That said, I'll see if I can use some sort of macro or something like constexpr to generate a table in C++ and then export that to C. Also, unfortunately needing to have an int that dispatches to other data is required for interop reasons; if I could just pass class/struct references directly I would but that's sadly not particularly feasible here.
You said you wanted a C++ solution. Now you say you want a C++ solution that you want to access from C. You need to be exactly clear on what you want.
This is certainly not accessible from C. Why do you want to go through all these hoops. Just write it in C.
Cause writing it in C is also pretty far from clean, however after some experimentation I agree that probably just sticking to C will unfortunately be easier.
0

So as pointed out in some comments, C++ 20 does support designated initializers and instead I was running into an issue with the Intellisense on VSCode's C++ extension not supporting them with default settings on Windows (regardless of how the code actually gets built). So overall that should be sufficient to enforce ordering and allow C interop.

1 Comment

I've marked you down here. This is not an answer. If you intend to answer your own question then provide code that answers the question you asked not a general comment about C++'s capabilities.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.