Skip to content

refactor(enginebuffer): Make EngineBuffer::KeylockEngine an enum class#27

Merged
ronso0 merged 1 commit into
ronso0:rubberband3-switchfrom
Swiftb0y:enginebuffer-keylockengine-improvements
Aug 3, 2022
Merged

refactor(enginebuffer): Make EngineBuffer::KeylockEngine an enum class#27
ronso0 merged 1 commit into
ronso0:rubberband3-switchfrom
Swiftb0y:enginebuffer-keylockengine-improvements

Conversation

@Swiftb0y
Copy link
Copy Markdown

@Swiftb0y Swiftb0y commented Aug 2, 2022

No description provided.

@ronso0
Copy link
Copy Markdown
Owner

ronso0 commented Aug 2, 2022

well, thanks!
I'm still a c++ noob so I need someone else to review this. @daschuer could you take a look?

Comment thread src/engine/enginebuffer.h Outdated
Comment thread src/engine/enginebuffer.h
KeylockEngine::SoundTouch,
KeylockEngine::RubberBandFaster,
KeylockEngine::RubberBandFiner,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This duplication is a source of later error.

I am not against using an enum class here. But just because this is a good example how software paradigms and maintainability/readability are not always the same, let me share my view:

enum classes have been introduced for two reasons

  • A protected namespace
  • type-safty

Part of the type safety is that the value representation can be random. There is no iterator, no compare operator and no ++ operator or implicit numeric value cast because of that reason, this protects the developer from doing wrong.

However, in our case we need exactly this. The enum is in fact just a util to not deal with the magic numbers from the mixxx.cfg file in our source code.

We need control over the value, we need the integer presentation and an iterator, the sings that have been removed from the enum class by design. So from the type-safty aspect an plain old Enum is a better fit ... but without the protected namespace we reintroduce a "strong" issue.

So I propose to reintroduce just the value KeylockEngine::Count and the integer loop in this case.

The alternative would be to add the missing operators to this enum class https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Renum-oper or introduce a generic solution like shown here: https://stackoverflow.com/a/8498694

Copy link
Copy Markdown
Author

@Swiftb0y Swiftb0y Aug 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your review is reasonable and I have a couple notes:

So I propose to reintroduce just the value KeylockEngine::Count and the integer loop in this case.

I strongly object to this. As you said yourself, we can't be sure about the underlying value. Adding a last entry and assuming it indicates how many constants the enum contains is IMO a bad code smell. The current approach is very careful to not cause undefined behavior. As such, casting from some int to KeylockEngine was carefully avoided. We can't assume that the enum values start at 0 and are consecutive. For example if we ever removed Soundtouch, we can't remove the Soundtouch entry from the enum. We'd also have to modify the loop that loop that does the static cast hack, etc. All of which could cause bugs and UB and from what I can tell, the compiler can't warn us about.

So we need an iterator, if I were to define an iterator on the Keylock enum, the result would essentially be another giant switch-case mess, because due to the reasons I mentioned above, static_cast<KeylockEngine>(++static_cast<int>(engine)) won't work.

The problem with the value representation being random is also not a problem when provide explicit values (as suggested in my own comment above).

My initial plan was to make kKeylockEngines be kAvailableKeylockEngines and then refactor the other querying methods in terms of that, but that either resulted in unnecessary interdependencies or complex code that would be objected to.

This duplication is a source of later error.

Most of the times yes, but here, the duplication is right next to each other and has a clear purpose.

keylockComboBox->clear();
for (int i = 0; i < EngineBuffer::KEYLOCK_ENGINE_COUNT; ++i) {
const auto engine = static_cast<EngineBuffer::KeylockEngine>(i);
for (const auto engine : EngineBuffer::kKeylockEngines) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually need an int below in this loop, so there is not much a point of using a range based loop.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As pointed out above, I didn't want to do that because I wanted to avoid the cast from int to Keylock engine when I couldn't be sure that the values actually came from Keylockengine in the first place. So this solution is somewhat more typesafe.

Copy link
Copy Markdown
Author

@Swiftb0y Swiftb0y Aug 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the problem is that we're converting to an int so we can use it as a QVariant, but throwing the enum value into QVariant right of the bat would make much more sense. Let me fix that.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Copy Markdown

@daschuer daschuer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, Thank you.

I think our different view is sourced by the question what is the leading value type. Is it the constitutive integer from the mixxx.cfg or the Enum Class managed by the compiler. Before you last change we where on the middle ground. Now you have pushed it completely to the later.
This is OK to me.

However let's clarify one thing: In C++ the integer representation of of the enum is well defined:
https://en.cppreference.com/w/cpp/language/enum

Each enumerator is associated with a value of the underlying type. When initializers are provided in the enumerator-list, the values of enumerators are defined by those initializers. If the first enumerator does not have an initializer, the associated value is zero. For any other enumerator whose definition does not have an initializer, the associated value is the value of the previous enumerator plus one.

This means, adding a last value and use it in an iterator is also a valid and reliable solution.

@ronso0 You may hit merge now. Thank you.

@ronso0
Copy link
Copy Markdown
Owner

ronso0 commented Aug 3, 2022

Great, thanks!

@Swiftb0y wanna squash the fixup or should I squash all commits?

@Swiftb0y
Copy link
Copy Markdown
Author

Swiftb0y commented Aug 3, 2022

This means, adding a last value and use it in an iterator is also a valid and reliable solution.

Well sort of. Yes, the semantics are fortunately well defined, but that doesn't mean that its still a good solution. As I pointed out, it could be in the future that the enum values become non-consecutive for some reason. So that't why I wanted to avoid the consecutive iteration. I also don't like that approach semantically. IMO an enum is supposed to group related constants together. So the length of the enum is not related to the rest of the constants and thus should not be an enumerator. IMO this is somewhat of a short-coming in c++. There should be a std::size specialization for enum classes or something.

Before you last change we where on the middle ground. Now you have pushed it completely to the later.
This is OK to me.

Can you elaborate? I didn't want to bully you into accepting my solution. Did we come to consensus we are both ok with?

wanna squash the fixup or should I squash all commits?

I kinda messed up the fixup. I'll just squash everything into a single commit.

refactor(prefs): improve keylockengine-related typesafety
@Swiftb0y Swiftb0y force-pushed the enginebuffer-keylockengine-improvements branch from 22ecd64 to 02bbeb2 Compare August 3, 2022 12:48
@daschuer
Copy link
Copy Markdown

daschuer commented Aug 3, 2022

Can you elaborate? I didn't want to bully you into accepting my solution.

You solution is now good, since you did the extra mile. Thank you.

@Swiftb0y
Copy link
Copy Markdown
Author

Swiftb0y commented Aug 3, 2022

Thank you for the review.

@ronso0 ronso0 merged commit d92a6bf into ronso0:rubberband3-switch Aug 3, 2022
@ronso0
Copy link
Copy Markdown
Owner

ronso0 commented Aug 3, 2022

🎉 Thanks to both of you!

@Swiftb0y Swiftb0y deleted the enginebuffer-keylockengine-improvements branch August 3, 2022 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants