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

Would it possible to have more educational things supported? #557

Open
GKxxUCAS opened this issue Aug 20, 2023 · 6 comments
Open

Would it possible to have more educational things supported? #557

GKxxUCAS opened this issue Aug 20, 2023 · 6 comments

Comments

@GKxxUCAS
Copy link

This is a very useful project that helps reveal the things that happen under some fancy syntaxes of C++. As a teaching assistant of the C++ programming course in my university I would very much like to use it to assist with teaching.

The educational coroutines transformation is perfect for learning coroutines. I wanted some other educational tools and was wondering whether they can be integrated into C++ insights. Here are some ideas I could think of:

  • Better preprocessor output: may be used to explain how include guards work and how circular includes happen. I want something better than what is obtained from the -E compiler flag, like folding or indentations to help see the include structure better.

  • Hand-coded compiler-generated special member functions. For example:

    #include <string>
    struct A {
      int x;
      std::string s;
    };
    int main() {
      A a, b(a);
    }

    As for the copy constructor of A, current C++ insights only shows

      // inline constexpr A(const A &) noexcept(false) = default;

    but it might be more helpful to show something like this

      inline constexpr A(const A &other) noexcept(false)
          : x(other.x), s(other.s) {}
  • Transformation of member functions into non-member functions, just like what Cfront did. This may help students understand how member functions are called, the this pointer, the cv-qualification on member functions, etc. For example: Transform

    struct A {
      int x;
      int get_x() const {
        return x;
      }
    };
    int main() {
      A a;
      return a.get_x();
    }

    into

    struct A {
      int x;
    };
    int __A_memfun_get_x(const A *this) {
      return this->x;
    }
    int main() {
      A a;
      return __A_memfun_get_x(&a);
    }

Looking forward to your reply.

@andreasfertig
Copy link
Owner

Hello @GKxxQAQ,

The educational coroutines transformation is perfect for learning coroutines. I wanted some other educational tools and was wondering whether they can be integrated into C++ insights. Here are some ideas I could think of:

I'm glad that C++ Insights helps you with teaching C++. It's why I created the tool and where I use it the most.

Regarding your question, I'm generally open to adding more educational transformations. However, they are the ones with the most effort in development and maintenance. Plus, the risk of showing something incorrect is higher. That means there should be a great benefit such that the long-term effort is worth it.

* Better preprocessor output: may be used to explain how include guards work and how circular includes happen. I want something better than what is obtained from the `-E` compiler flag, like folding or indentations to help see the include structure better.

C++ Insights leaves the preprocessor alone. It only receives the output from the preprocessor run. This can be changed, but I need (very) a convincing example of how it helps.

  but it might be more helpful to show something like this
  ```c++
    inline constexpr A(const A &other) noexcept(false)
        : x(other.x), s(other.s) {}
  ```

I agree. Clang doesn't give this information away, so I must hand-code everything. I'm open for a patch.

* Transformation of member functions into non-member functions, just like what Cfront did. This may help students understand how member functions are called, the `this` pointer, the cv-qualification on member functions, etc. For example: Transform

I let you in on a secret (and obviously everybody who reads this public message :-). I have a Cfront mode in a development branch for about a year. I use it in my training classes. The transformations there are huge, and I keep finding bugs. Hence, I haven't released this transformation yet. However, I plan to publish it once I find enough time again, hopefully around this year.

Andreas

@GKxxUCAS
Copy link
Author

GKxxUCAS commented Sep 19, 2023

Hi. Thank you for replying to me!

As for the compiler-generated special member functions, recently I'm also learning clang libtooling and I am trying to implement a tool (not yet finished) that can hand-code the implicitly-defined functions. Inherited constructors are also supported. For example, for the following code

#include <vector>

class Node {};

class BinaryOp {
  Node left, right;

 public:
  BinaryOp(const Node &l, const Node &r = {}) : left{l}, right{r} {}
  BinaryOp() = default;
};

struct PlusOpImpl {};

struct OpCommon {};

class PlusOp : public OpCommon, public BinaryOp, private PlusOpImpl {
  std::vector<int> m_operands;
  std::size_t m_size{};

  using BinaryOp::BinaryOp;
};

int main() {
  PlusOp po({}, {});
  BinaryOp bo;
  BinaryOp bo2({}, {});
  PlusOp po2;
  PlusOp po3(po2);
}

The output is:

// For demonstration purposes only.
// Indicates the "more-than-perfect" forwarding of arguments that happens
// in inherited constructors.
// std::forward is not perfect enough, because it is not used together
// with universal references.
template <typename T>
auto perfectly_forward(T &&other);

#include <vector>

class Node {
 public:
  // trivial.
  inline constexpr Node() noexcept {}

  // trivial.
  inline constexpr Node(const Node &) noexcept {}

  // trivial.
  inline ~Node() noexcept {}

};

class BinaryOp {
  Node left;
  Node right;


 public:
  BinaryOp(const Node &l, const Node &r = {}) : left{l}, right{r} {}

  // trivial, explicitly defaulted.
  inline constexpr BinaryOp() noexcept
  // The subobjects are initialized as follows:
  //    left is default-initialized (calling Node::Node()).
  //    right is default-initialized (calling Node::Node()).
  {}

  // trivial.
  inline constexpr BinaryOp(const BinaryOp &other) noexcept
      : left(other.left),
        right(other.right) {}

  // trivial.
  inline ~BinaryOp() noexcept {}
  // The subobjects are destroyed as follows:
  //    right.~Node();
  //    left.~Node();

};

struct PlusOpImpl {
  // trivial.
  inline constexpr PlusOpImpl() noexcept {}

  // trivial.
  inline constexpr PlusOpImpl(const PlusOpImpl &) noexcept {}

  // trivial.
  inline ~PlusOpImpl() noexcept {}

};

struct OpCommon {
  // trivial.
  inline constexpr OpCommon() noexcept {}

  // trivial.
  inline constexpr OpCommon(const OpCommon &) noexcept {}

  // trivial.
  inline ~OpCommon() noexcept {}

};

class PlusOp : public OpCommon, public BinaryOp, private PlusOpImpl {
  std::vector<int> m_operands;
  std::size_t m_size {};


 public:
  inline PlusOp() noexcept
  // The subobjects are initialized as follows:
  //    base class OpCommon is default-initialized (calling OpCommon::OpCommon()).
  //    base class BinaryOp is default-initialized (calling BinaryOp::BinaryOp()).
  //    base class PlusOpImpl is default-initialized (calling PlusOpImpl::PlusOpImpl()).
  //    m_operands is default-initialized (calling vector<int>::vector<int>()).
  //    m_size is list-initialized from {} (in-class init).
  {}

  inline PlusOp(const PlusOp &other) noexcept(false)
      : OpCommon(other),
        BinaryOp(other),
        PlusOpImpl(other),
        m_operands(other.m_operands),
        m_size(other.m_size) {}

  // inherited by using BinaryOp::BinaryOp;
  inline PlusOp(Node const &__arg0, Node const &__arg1 = {}) noexcept(false)
      : //    base class OpCommon is default-initialized (calling OpCommon::OpCommon()).
        BinaryOp(perfectly_forward(__arg0),
                 perfectly_forward(__arg1))
        //    base class PlusOpImpl is default-initialized (calling PlusOpImpl::PlusOpImpl()).
        //    m_operands is default-initialized (calling vector<int>::vector<int>()).
        //    m_size is list-initialized from {} (in-class init).
  {}

  inline ~PlusOp() noexcept {}
  // The subobjects are destroyed as follows:
  //    m_size (of type std::size_t) is destroyed, which has no effect.
  //    m_operands.~vector<int>();
  //    PlusOpImpl::~PlusOpImpl();
  //    BinaryOp::~BinaryOp();
  //    OpCommon::~OpCommon();

};

int main() {
  PlusOp po({}, {});
  BinaryOp bo;
  BinaryOp bo2({}, {});
  PlusOp po2;
  PlusOp po3(po2);
}

I'm so surprised to hear that you have implemented a Cfront mode! That's super cool. I'm looking forward to the release of it.

@GKxxUCAS
Copy link
Author

GKxxUCAS commented Sep 22, 2023

@andreasfertig While implementing the tool mentioned above I found that Clang sometimes doesn't declare some of the special member functions, which the standard require to be implicitly-declared. Even those that are odr-used (which should be implicitly-defined) can be left out, since to be standard-conforming it only needs to act as if those functions are declared/defined. For example

struct X {
  int a{0};
};
int main() {
  X x;
  return x.a;
}

The destructor of X is odr-used, hence implicitly-defined, according to the standard. But Clang AST does not show this, neither does CppInsights. Moreover, both the copy assignment operator and the move assignment operator of X should be implicitly-declared, but neither appears in the Clang AST. I solved this partially by Sema::ForceDeclarationOfImplicitMembers, but this only declares X::~X() instead of defining it. I'm still looking for solutions to figure out whether an implicitly-declared destructor should be defined. Sema::getUndefinedButUsed() generates an empty vector in this case, which really confuses me. Maybe this should also be fixed in CppInsights? Do you know anything more on this?

@andreasfertig
Copy link
Owner

Hello @GKxxQAQ,

nice work! I wasn't aware of Sema::ForceDeclarationOfImplicitMembers. I often use C++ Insights to also show the tiny optimizations compilers do, like not creating a destructor if none is required. At least for my usage, I'm happier without the special member functions that are not there in the end. However, I'm happy to enable this with an option if there is a strong desire.

Andreas

@GKxxUCAS
Copy link
Author

@andreasfertig I'm so happy to find that you have released the Cfront mode transformations, and the transformations are a lot more than I expect!

A slight mistake: Maybe the comment in CodeGenerator.h on line 599-600 is incorrect.

@andreasfertig
Copy link
Owner

Hello @GKxxQAQ,

I'm so happy to find that you have released the Cfront mode transformations, and the transformations are a lot more than I expect!

My pleasure!

A slight mistake: Maybe the comment in CodeGenerator.h on line 599-600 is incorrect.

Thanks for spotting this! I will fix it with one of the upcoming fixes.

Andreas

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

2 participants