Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modernizing c++ code #8491

Open
tsjung75 opened this issue Jan 19, 2025 · 4 comments
Open

Modernizing c++ code #8491

tsjung75 opened this issue Jan 19, 2025 · 4 comments

Comments

@tsjung75
Copy link

Do you have any plan to modernize generated c++ code by using c++ 23?

Not just using std::optional or std::span, also complete rewriting for simplicity, readability like protobuf.
Cascading get/set, simplest easist API.

@dbaileychess
Copy link
Collaborator

I would like to modernize the code used in flatc to be more modern, as it easier to build that with modern toolchains/compilers. The support for the generated code still has to target older standards (we still support C++11), so I don't envision that being changed any time soon. We could add targeted support for C++20 and C++23 features, like we have for C++17, but it wouldn't be a full rewrite.

It just a lot of work to do.

@ZERO-70
Copy link

ZERO-70 commented Jan 25, 2025

Here are some tips:

1. Adopt C++23 Features

C++23 introduces several features that can simplify and modernize your code. Here are some key features to consider:

a. std::optional and std::expected

  • Replace raw pointers or error-prone return types with std::optional or std::expected for better expressiveness and safety.
  • Example:
    std::optional<std::string> get_name() const {
        if (name.empty()) return std::nullopt;
        return name;
    }

b. std::span for Arrays and Buffers

  • Use std::span to represent non-owning views over arrays or buffers, avoiding raw pointers and manual size tracking.
  • Example:
    void process_data(std::span<int> data) {
        for (auto& item : data) {
            // Process item
        }
    }

c. Deducing this (C++23)

  • Simplify member function overloads by deducing the this type.
  • Example:
    struct MyClass {
        void do_something(this auto&& self) {
            // Works for lvalue and rvalue objects
        }
    };

d. std::format for String Formatting

  • Replace printf or manual string concatenation with std::format for type-safe and readable string formatting.
  • Example:
    std::string message = std::format("Hello, {}! The answer is {}.", name, 42);

e. Ranges and Views

  • Use the C++20 ranges library (improved in C++23) for more expressive and functional-style operations on collections.
  • Example:
    auto even_numbers = data | std::views::filter([](int x) { return x % 2 == 0; });

f. Modules (C++20, improved in C++23)

  • Replace #include with modules for faster compilation and better encapsulation.
  • Example:
    import my_module;

g. Concepts (C++20, improved in C++23)

  • Use concepts to constrain templates and improve error messages.
  • Example:
    template <std::integral T>
    T add(T a, T b) {
        return a + b;
    }

2. Simplify APIs

To create a simple and intuitive API, consider the following:

a. Cascading Get/Set Methods

  • Use method chaining for fluent APIs.
  • Example:
    class Person {
    public:
        Person& set_name(const std::string& name) {
            this->name = name;
            return *this;
        }
    
        Person& set_age(int age) {
            this->age = age;
            return *this;
        }
    
    private:
        std::string name;
        int age;
    };
    
    // Usage
    Person p;
    p.set_name("Alice").set_age(30);

b. Builder Pattern

  • Use the builder pattern for complex object construction.
  • Example:
    class PersonBuilder {
    public:
        PersonBuilder& with_name(const std::string& name) {
            person.set_name(name);
            return *this;
        }
    
        PersonBuilder& with_age(int age) {
            person.set_age(age);
            return *this;
        }
    
        Person build() {
            return std::move(person);
        }
    
    private:
        Person person;
    };
    
    // Usage
    Person p = PersonBuilder().with_name("Bob").with_age(25).build();

c. Default Arguments and Named Parameters

  • Use default arguments and named parameters (via structs) to simplify function calls.
  • Example:
    struct Config {
        std::string name = "default";
        int age = 0;
    };
    
    void initialize(const Config& config = {}) {
        // Use config.name and config.age
    }
    
    // Usage
    initialize({.name = "Charlie", .age = 40});

3. Rewrite for Readability

Focus on making the codebase more readable and maintainable:

a. Use Meaningful Names

  • Replace cryptic variable and function names with descriptive ones.

b. Reduce Boilerplate

  • Use auto, structured bindings, and other modern features to reduce verbosity.
  • Example:
    auto [name, age] = get_person();

c. Leverage RAII

  • Ensure resources are managed using RAII principles, avoiding manual memory management.

d. Consistent Style

  • Adopt a consistent coding style and enforce it with tools like clang-format.

4. Tooling and Testing

  • Use modern tools to ensure quality and compatibility:
    • Compiler: Ensure your compiler supports C++23 (e.g., GCC 13+, Clang 16+, MSVC 2022+).
    • Static Analysis: Use tools like clang-tidy to catch potential issues.
    • Testing: Write unit tests to ensure the modernized code behaves as expected.

5. Incremental Modernization

  • Modernize the codebase incrementally to avoid breaking changes:
    1. Identify critical or frequently used components.
    2. Modernize these components first.
    3. Gradually refactor the rest of the codebase.

Example: Modernized Protobuf-like API

Here’s an example of how you might modernize a simple protobuf-like class:

class Person {
public:
    // Cascading setters
    Person& set_name(std::string name) {
        this->name = std::move(name);
        return *this;
    }

    Person& set_age(std::optional<int> age) {
        this->age = age;
        return *this;
    }

    // Getters
    std::optional<std::string> get_name() const {
        return name;
    }

    std::optional<int> get_age() const {
        return age;
    }

    // Serialization (using std::format)
    std::string serialize() const {
        return std::format("Name: {}, Age: {}", name.value_or("Unknown"), age.value_or(0));
    }

private:
    std::optional<std::string> name;
    std::optional<int> age;
};

// Usage
Person p;
p.set_name("Alice").set_age(30);
std::cout << p.serialize() << std::endl;

I hope this may help 😄

@tsjung75
Copy link
Author

tsjung75 commented Jan 25, 2025

@dbaileychess
Thank you very much.
I understand your concern :)

@ZERO-70
I totally agree with your suggestion :)

I wish flatc would generate code in this style as well. Considering the current shift towards C++26, C++11-style code feels outdated. On top of that, it would be great if union were also replaced with std::variant.

@ZERO-70
Copy link

ZERO-70 commented Jan 25, 2025

I totally agree with your wish! With C++ moving forward so quickly, it makes sense for tools like flatc to adopt modern standards. Using std::variant instead of unions would definitely make the generated code safer and more in line with current best practices. Hopefully, they consider this in future updates!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants