Function calls in keymap definition

Is it possible to use a function call within the brace initializer that’s used to define the keymap in the sketch file? I’m thinking of trying an alternative to the somewhat awkward way of defining Qukeys that exists now, but it would require a function call that would take two Key values as parameters, and return a special Key value that encodes a serial number (via an incremented static variable), and as a side effect, defines an entry in an array that stores the two parameter values in an object. This would let someone write something like this as a keymap entry:

QK(Key_A, Key_LeftControl)

…and which defines the appropriate object in the Qukeys array as a side effect. I just don’t know if this is possible in C++ (and I don’t currently have access to a machine that can test this out).

It is possible to place function pointers in data structures. It is not possible to do so on the keymap, because pointers are 16bit wide, and would take up the entire key, and you would not be able to distinguish a pointer from any other value.

You could do this, but then you’d have to make all keys functions. That’s probably not the fastest thing…

I don’t think I made myself clear. What I would like to do, if possible, is call a function to define a key at compile time, setting Key.raw for that entry to the return value of that function.

Long term, I think it’s something that would be very, very powerful, if we can figure out a way to make it not hurt too badly.

It does sound like an interesting idea, but not especially different from Kaleidoscope-Macros, from a user point of view. It’s also not at all what I’m trying to ask about. I don’t want to store a function pointer in the keymap; I want to set the value of a keymap entry by calling a function at compile time, and set up other things with that function’s side effects. If that’s possible (I’m pessimistic about the compiler allowing it, though).

Long-term, I think it makes more sense to use a sketch-builder program to work around the limitations of the compiler, and to vastly improve the readability of the keymap configuration.

If you’re willing to share, it’d be useful to understand your specific desired end behavior.

In its current incarnation, Qukeys are configured outside the keymap, with a separate declaration, so (for example) the keymap entry might look like this:

Key_A

…and the qukey constructor is in a separate place:

Qukey(QWERTY, 2, 1, Key_LeftShift);

@algernon had the idea that we could use the DualUse declarations in the keymap as Qukeys, which would work, but would be more limited than the separate definition:

SFT_T(A)

I’m thinking of trying to put this in the keymap:

QK(Key_A, Key_LeftShift)

…or some abbreviated equivalent:

QK(A, LShift)

…where QK() is a macro that expands to some function call, such as:

#define QK(pri, alt)  defineQukey(pri, alt)

That function would be defined earlier, like so:

Key defineQukey(Key pri, Key alt) {
  static byte i = 0;
  // create the entry in an external array
  qukeys[i] = Qukey(pri, alt);
  // mark the keymap entry as a qukey, with an indexed offset
  return (Key){ .raw = ranges::QK_FIRST + i++ };
}

Then I’m back to the problem of getting the size of the array correct in its declaration, but right now I’m just wondering if this is feasible. I wouldn’t use a static variable in the function, of course; that index would end up being the size of the array, so it would be a class variable instead, but I hope that conveys the idea.

It’s like the DualUse declarations in the keymap, but would allow for the full range of possible Key values for both the primary and alternate Keys. I’m interested in the question of whether or not this idea is feasible (I’d be surprised if I tried this example and it worked), setting aside the issue of getting the size of the static qukeys[] array correct for the moment. I’m not convinced that this is a good solution to the problem (I’m pretty skeptical, myself) — I just want to know what’s possible at this point.

Oh, my bad. Sorry for the misunderstanding! I do not know of a way to achieve running a custom function at compile time, though. Highly doubt it is possible, and definitely not with side effects.

Yeah, that’s not possible in C++.

If we used a Lisp… if we had a Lisp that can create efficient AVR code, now that would be amazing. And with Lisp macros, you could do something like this.

2 Likes

constexpr functions can run at compile time, but C++11 does not allow them to have local static variables. It isn’t possible using a class either, even if it had constexpr methods, because a constexpr object cannot be altered after construction.

1 Like

This idea must be handled with care. Even if it wasn’t a problem of distinction for Keys, it would be definitely non-portable. It would e.g. already break Kaleidoscope-Hardware-Virtual as this would require storing a 32/64 bit address in a 16 bit integer :skull_and_crossbones:

2 Likes

Thanks, everyone. I expected as much.

I doubt this would change anything, but what if it was a plain global variable?

This can be generalized to any constexpr intrinsic or objects variables except for local variables of constexpr functions. Also to global constexpr variables of any type. I am afraid, the answer is no.

What about a non-constexpr global variable?

Not accessible at compiletime.

1 Like

I’ll just have to write a sketch builder, then. There’s so much that’s known at compile time (or before) that C++ doesn’t provide any way to set…

I know how you feel. I were at this point a hundred times before. Wishing that the C++ pre-processor was a proper programming language that would enable global stateful variables at compiletime.

1 Like

Hm. Call me crazy, but isn’t the template metaprogramming language of C++ turing-capable? That might allow solving the conundrum, by having e.g. QK<Key_LeftShift>(Key_A) as syntax, but it’d require representing all keys at the type level as wel. Not the prettiest solution, I admit :wink:

:scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream::scream:

I think that adequately expresses my feelings. :wink:

3 Likes

TMP is absolutely not the solution to this problem. Python & Perl are also Turing-complete, and in those languages, it’s actually possible to write comprehensible code that could generate a sketch file (or the sections of it that would be necessary to make the keymap more readable, anyway).

The turing machine is also turing complete, and much more simple than TMP. Why not… :crazy_face:

All jokes asside. It would be fairly simple to code the keymap as A std::tuple template type. But that does not make the information available in other compilation units. It would require to define the keymap in a header file and include it in all places where the information would be required. But this helps only at compile time. There are no types at runtime! The information would need to be mapped to bits and bytes to be available at runtime…say, an array, and there we are back again.

There would yet be some benefit in such a solution. Different entries in the keymap could have different bitness and @merlin could easily store his qukey function pointer.