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

Fail to compile if attempting to specifying MI bases via class_ ctor parameters #742

Merged
merged 1 commit into from
Mar 17, 2017

Conversation

jagerman
Copy link
Member

@jagerman jagerman commented Mar 16, 2017

Currently we only allow multiple inheritance via class_ template parameters; this extends the support to class_ instances passed via the derived class_ constructor arguments as well.

Edit: see discussion below. We can't support MI via parameters in the most useful case, so produce a compile time error instead.

Fixes #741

@wjakob
Copy link
Member

wjakob commented Mar 16, 2017

neat! That looks great (including your implementation)

@wjakob
Copy link
Member

wjakob commented Mar 16, 2017

Looks like MSVC doesn't like your template forward declaration.

@dean0x7d
Copy link
Member

As far as I have gathered, the main motivation for the alternative constructor-argument base specification is this: http://pybind11.readthedocs.io/en/latest/advanced/misc.html#partitioning-code-over-multiple-extension-modules

If that's the case, the class_<...> compile-time information is lost and it reverts to adding bases via handle and the original problem comes back. It would be useful to add an error message in that case.

@jagerman
Copy link
Member Author

Ah, right, that's going to be a problem.

@dean0x7d
Copy link
Member

dean0x7d commented Mar 16, 2017

If the compile-time type information is always required for multiple inheritance, perhaps it would be enough to require that MI must use the template parameter specification, limit the ctor arguments to single-inheritance-only and just report an error if > 1 handles are passed via ctor.

@jagerman
Copy link
Member Author

Yeah, that sounds reasonable.

@jagerman
Copy link
Member Author

Even better: it should be possible to make it a compile-time error.

@wjakob
Copy link
Member

wjakob commented Mar 16, 2017

@dean0x7d: I'm not sure I follow -- why is compile-time information needed for MI? The documentation you referenced talks about cyclic dependencies, but that's not the case here, no?

@jagerman
Copy link
Member Author

@wjakob - because MI requires a static_cast<BaseN>(reinterpret_cast<Derived>(voidptr)): unlike single inheritance, we can't reinterpret_cast directly to the BaseN because BaseN might not be at the pointer itself.

@wjakob
Copy link
Member

wjakob commented Mar 17, 2017

ah right.. I forgot about that. We need to create all the converters that let us jump through the hierarchy.

@jagerman
Copy link
Member Author

So while we could support it for actual class_<Base, ...> instances, it's probably not worth bothering with since we can't support it in the most useful base-class-as-argument case.

@jagerman jagerman changed the title Allow specifying MI bases via class_ ctor parameters Fail to compile if attempting to specifying MI bases via class_ ctor parameters Mar 17, 2017
@jagerman jagerman force-pushed the mi-class-args branch 5 times, most recently from d9bd3fa to a07094f Compare March 17, 2017 06:31
none_of<is_pyobject<Extra>...>::value || // no base class arguments, or:
( constexpr_sum(is_pyobject<Extra>::value...) == 1 && // Exactly one base
constexpr_sum(is_base<options>::value...) == 0 && // no template option bases
none_of<std::is_same<multiple_inheritance, Extra>...>::value), // no multiple_inheritance attr
Copy link
Member

Choose a reason for hiding this comment

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

It's pretty awesome that this can be a compile-time error!

One minor point, and feel free to ignore this since it's more about code style. The first none_of and the first constexpr_sum are repeating a bit of work. Consider assigning the sum to a variable to reuse the result. In general, assigning to variables would make the code more self-documenting, avoiding the need for any extra comments. E.g.:

constexpr auto compiletime_bases = constexpr_sum(is_pyobject<options>::value...);
constexpr auto runtime_bases     = constexpr_sum(is_pyobject<Extra>::value...);
constexpr auto needs_multiple_inheritance = compiletime_bases + runtime_bases > 1 ||
                                            any_of<std::is_same<multiple_inheritance, Extra>...>();
static_assert(!(needs_multiple_inheritance && runtime_bases > 0),
              "Error: multiple inheritance bases must be specified via class_ template options");

(In addition to the variable names, the error message itself shows the overall intent.)

Copy link
Member Author

Choose a reason for hiding this comment

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

There's one annoying issue (MSVC related, as usual) with doing it that way: MSVC considers all the constexpr variables unused if only used in the static_assert, and so issues a warning which breaks the build, and so separate variables would need a (void) (compiletime_bases, runtime_bases, needs_mi); to silence it, which is a bit ugly.

I thought it might also end up being potentially less work for the compiler this way: typically the number of base classes is 0, so short-circuiting avoids needing to compute everything but the first one.

Copy link
Member Author

Choose a reason for hiding this comment

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

Given the MSVC warning bug, I think I'll merge as is.

Copy link
Member

Choose a reason for hiding this comment

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

Sure, better than adding workarounds.

We can't support this for classes from imported modules (which is the
primary purpose of a ctor argument base class) because we *have* to
have both parent and derived to properly extract a multiple-inheritance
base class pointer from a derived class pointer.

We could support this for actual `class_<Base, ...> instances, but since
in that case the `Base` is already present in the code, it seems more
consistent to simply always require MI to go via template options.
@jagerman
Copy link
Member Author

Rebased to make sure this works with MSVC 2017 (MSVC 2015u3 was very fragile around the static_assert: changing the constexpr_sum(..) == 0 bit to a none_of<..> made it sometimes fail to compile with a "not a constexpr value" error, and other times ICE at link time, depending on how exactly I wrote the expression.)

@jagerman jagerman merged commit b961626 into pybind:master Mar 17, 2017
@jagerman jagerman modified the milestone: v2.1 Mar 19, 2017
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

Successfully merging this pull request may close these issues.

3 participants