-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Address regression introduced in #5381 #5396
Conversation
@rwgk let's move the rest of the discussion here. I implemented the patch we discussed in #5381 (comment) and #5381 (comment) but that still does not fix the regression on my actual use case, hence this PR is still marked as draft. I think that both of us were expecting I'll post a link to this PR in the downstream project, but I can't see a way for us downstream to work around the compilation error if the regression isn't sorted. The forward declared variable is a pointer to a class which is part of the private interface of a third-party library, over which we have no control. |
Sigh again. I've stumbled over that pattern several times before. Fundamentalistically speaking, if it's meant to be private, it shouldn't be exposed to Python. More pragmatically speaking: This really is in the gray area of what you can expect a bindings system to support. It just so happened that it used to work. Corner case. To restore supporting this corner case, I think you need to experiment. I'd bet you can make it work again, but not sure, and not sure how. I'd start with It looks a little tricky to fit that into the existing machinery. Alternatively, I'd look into changing the bindings code, to avoid exposing the private C++ type to Python. |
Not really. The class itself is private, but the pointer to the class is public. It will be exposed to Python by someone else, in a way we have no control over. Hence
is a no go.
Will try. |
@francesco-ballarin can you help me understand the problem better. #include <iostream>
// Start pybind11 intrinsic_type
/// Helper template to strip away type modifiers
template <typename T>
struct intrinsic_type {
using type = T;
};
template <typename T>
struct intrinsic_type<const T> {
using type = typename intrinsic_type<T>::type;
};
template <typename T>
struct intrinsic_type<T *> {
using type = typename intrinsic_type<T>::type;
};
template <typename T>
struct intrinsic_type<T &> {
using type = typename intrinsic_type<T>::type;
};
template <typename T>
struct intrinsic_type<T &&> {
using type = typename intrinsic_type<T>::type;
};
template <typename T, size_t N>
struct intrinsic_type<const T[N]> {
using type = typename intrinsic_type<T>::type;
};
template <typename T, size_t N>
struct intrinsic_type<T[N]> {
using type = typename intrinsic_type<T>::type;
};
template <typename T>
using intrinsic_t = typename intrinsic_type<T>::type;
// End pybind11 intrinsic_type
class Test1;
typedef struct Test1* Test2;
template <typename Arg>
using f1 = std::is_same<Test1, intrinsic_t<Arg>>;
template <typename Arg>
using f2 = std::is_base_of<Test1, intrinsic_t<Arg>>;
int main() {
std::cout << f1<Test2>::value << std::endl;
std::cout << f2<Test2>::value << std::endl;
return 0;
} |
Thanks @gentlegiantJGC for looking into this. I'd say that the following reproduces the issue starting from your minimal example. Note how I had to introduce the class
fails with
|
As I think you discovered in the From a quick google there are workarounds to tell if an argument is complete but they aren't very nice. |
I have a solution. Perhaps you can improve upon it #include <iostream>
#include <concepts>
template <typename, typename = void>
struct is_complete : std::false_type {};
template <typename T>
struct is_complete<T, decltype(void(sizeof(T)))> : std::true_type {};
class args {};
class ForwardClass;
class Class{};
class Args : public args {};
template<typename Base, typename Derived>
constexpr bool safe_is_base_of(){
if constexpr (is_complete<Derived>::value){
return std::is_base_of<Base, Derived>::value;
} else {
return false;
}
}
template <typename Arg>
using argument_is_args = std::conditional_t<
safe_is_base_of<args, Arg>(),
std::true_type,
std::false_type>;
int main() {
std::cout << argument_is_args<ForwardClass>() << std::endl;
std::cout << argument_is_args<Class>() << std::endl;
std::cout << argument_is_args<Args>() << std::endl;
return 0;
} I initially tried Edit: |
Looks great ❤️ . I've invited you to my fork, so that you can push to the branch associated with this PR if you wish. Once we iron out a fix I'll try it on the actual end user library. |
This can probably be done better but at least this is a start.
Erm okay. Apparently |
if constexpr was not added until C++ 17. I think this should do the same thing as before.
I think that change might work. I don't understand what |
Thanks @gentlegiantJGC , I can now confirm that the change in this branch fixes the downstream compilation issue in the end user library. @rwgk this is now ready for your review. |
include/pybind11/cast.h
Outdated
struct is_same_or_base_of : std::is_same<Base, Derived> {}; | ||
|
||
// Only evaluate is_base_of if Derived is complete. | ||
// It will raise a compiler error if Derived is not complete. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC: Neat! (I wish I had known the decltype(void(sizeof(Derived)))
trick before!)
However, I find this comment confusing, or I don't actually understand correctly.
Did you mean this?
// This specialization prevents a compiler error if Derived is not complete.
Also, where did you find the decltype(void(sizeof(Derived)))
trick?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was based on code from the top of here (linked in my earlier message)
https://devblogs.microsoft.com/oldnewthing/20190710-00/?p=102678
I don't know how official that is though.
I will make the comment more clear.
@francesco-ballarin do you want to write some test cases for this to make sure it doesn't get broken again in the future? |
That would be best, and I now believe it could be pretty simple, basically just a unit test ( |
Should there also be a test constructing a pybind class around a pointer to an incomplete type? |
I'd say: Great idea! But up to you. If it works without any further production code changes: fine to include in this PR. Otherwise I think it'll be better to merge this PR with a minimal new test, and start a new PR for what you have in mind. |
@gentlegiantJGC I agree it would be best to have a test that catches this, but I won't be capable of preparing one myself ;) |
Looks like most of the tests are passing. |
…ly with clang-tidy.
…e more distracting than helpful here).
My first reaction: I'm actually amazed that all tests pass, except clang-tidy. In theory we could suppress the clang-tidy warnings, but I'd rather not, unless we can build a convincing case that But let's take a step back: I added two commits: I'll merge this PR when I see that all GitHub Actions jobs pass. That will resolve the regression as soon as possible. |
Continuing the discussion here for now, but in a separate comment:
It does happen to work for passing around pointers to incomplete (wished-for "private") classes. But I'd argue it's a hack. I wouldn't want to take away the candy, but I also wouldn't want to disable clang-tidy warnings to support a hack. I'd say what we really want is a clean feature to support passing around pointers to incomplete (from the perspective of pybind11) types. I think it's feasible, and probably very little extra code, but it needs some experimentation. Which means:
|
I edited the PR description, mainly to restore the template for the description (so that I'm more sure the automatic harvesting will work) and to add a minimal Suggested changelog entry. That way we won't forget. The changelog entries are automatically harvested and then manually curated. In this case we'll combine the information from PRs 5381 and 5396. |
See #5409, which (currently) sketches out a way to cleanly support passing around pointers to incomplete (from the perspective of pybind11) types. |
Description
This PR fixes a regression introduced in #5381 that shows up (at least) when the
pybind11
wrapped type is a pointer to a forward declared class. In that case checking forstd::is_base_of<>
as in #5381 results in a compilation error because the forward declaration doesn't have enough information to determine the inheritance diagram.Suggested changelog entry: