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

Properly handle virtual base classes without default constructor #166

Open
virtuald opened this issue Oct 3, 2022 · 1 comment
Open

Comments

@virtuald
Copy link
Member

virtuald commented Oct 3, 2022

#pragma once

class MMBase {
public:
    explicit MMBase(int number, std::string name) : m_name(std::move(name)) {}
    virtual ~MMBase() = default;

    MMBase(const MMBase&) = delete;
    MMBase& operator=(const MMBase&) = delete;

    std::string getName() const { return m_name; }
private:
    std::string m_name;
};

class MMChild : public virtual MMBase {
public:
    MMChild(int number, std::string name) : MMBase(number, std::move(name)) {};
};

The only way I can currently bind this is by disabling trampolines, which disallows inheriting from these types using python. The reason is because the way I currently deal with this is like so:

template <typename PyTrampolineBase, typename PyTrampolineCfg>
struct PyTrampoline___MMBase : PyTrampolineBase,
                               virtual py::trampoline_self_life_support {
  using PyTrampolineBase::PyTrampolineBase;
};

// lots of other nonsense elided

The PyTrampolineBase template parameter can be the child class -- and the advantage of this is that our using declaration just brings in whatever constructors are defined in the child, and this works for grandchildren and subsequent descendants too -- while still providing proper bindings for each class being inherited.

Unfortunately, you can't do this with a virtual base class that doesn't have a default constructor, as it's undefined.

I experimented with a few things trying to understand this, and concluded that I don't really want to fix this right now -- I can just disable the trampolines for the specified type and it compiles. The core problem is that each level of inheritance that we use to compose the trampoline needs to have a constructor specific to the current class that we're defining a trampoline for. The current system assumes that the intermediate classes don't need to know anything about those inheriting from them, so this just isn't really possible.

One way this could be achievable is we could put all of the contents of the trampolines in separate include files, and compose them all that way instead... though, I think that might break in certain inheritance cases? There's a lot of bad solutions to this problem.

@virtuald
Copy link
Member Author

virtuald commented Oct 3, 2022

Here's a full cpp file that contains all the trampoline elements and fails to compile:

#include <string>

// bug where a virtual base with an explicit constructor wouldn't compile

class MMBase {
public:
  explicit MMBase(std::string name) : m_name(std::move(name)) {}
  virtual ~MMBase() = default;

  MMBase(const MMBase &) = delete;
  MMBase &operator=(const MMBase &) = delete;

  std::string getName() const { return m_name; }

private:
  std::string m_name;
};

namespace rpygen {
struct EmptyTrampolineCfg {};
}; // namespace rpygen

namespace py {
struct trampoline_self_life_support {
  trampoline_self_life_support() = default;
};
}; // namespace py

namespace rpygen {

template <typename CfgBase = EmptyTrampolineCfg>
struct PyTrampolineCfg___MMBase : CfgBase {
  using Base = ::MMBase;
};

template <typename PyTrampolineBase, typename PyTrampolineCfg>
struct PyTrampoline___MMBase : PyTrampolineBase,
                               virtual py::trampoline_self_life_support {
  using PyTrampolineBase::PyTrampolineBase;
};

}; // namespace rpygen

class MMChild : public virtual MMBase {
public:
  MMChild(int number, std::string name) : MMBase(std::move(name)){};
};

namespace rpygen {

template <typename CfgBase>
using PyTrampolineCfgBase___MMChild = PyTrampolineCfg___MMBase<CfgBase>;

template <typename CfgBase = EmptyTrampolineCfg>
struct PyTrampolineCfg___MMChild : PyTrampolineCfgBase___MMChild<CfgBase> {
  using Base = ::MMChild;
};

template <typename PyTrampolineBase, typename PyTrampolineCfg>
using PyTrampolineBase___MMChild =
    PyTrampoline___MMBase<PyTrampolineBase, PyTrampolineCfg>;

template <typename PyTrampolineBase, typename PyTrampolineCfg>
struct PyTrampoline___MMChild
    : PyTrampolineBase___MMChild<PyTrampolineBase, PyTrampolineCfg> {
  using PyTrampolineBase___MMChild<PyTrampolineBase,
                                   PyTrampolineCfg>::PyTrampolineBase___MMChild;
    // also insert here
};

}; // namespace rpygen

using MMChild_Trampoline = rpygen::PyTrampoline___MMChild<
    typename ::MMChild, typename rpygen::PyTrampolineCfg___MMChild<>>;


MMChild_Trampoline x(1, "h");

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

1 participant