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

[WIP] Fix ArrayView bool conversion #43

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Conversation

mosra
Copy link
Owner

@mosra mosra commented Apr 2, 2018

The problem, in short:

int a[5];
Debug{} << bool(ArrayView<int>{a}); // prints true, as expected
Debug{} << bool(ArrayView<int>{a, 4}); // prints true
Debug{} << bool(ArrayView<int>{a, 0}); // prints true .. huh?

Debug{} << bool(ArrayView<int>{nullptr}); // prints false, as expected
Debug{} << bool(ArrayView<int>{nullptr, 0}); // prints false
Debug{} << bool(ArrayView<int>{nullptr, 4}); // prints false .. huh?

In other words, currently bool conversion is done from the data pointer, not from the size pointer. I think it should depend on the size rather than on the data, to make life easier when writing parsers and such. The following code will cycle forever, because input is always some non-null pointer, even though size eventually becomes zero and one has to pay extra attention and say !input.empty() or something:

ArrayView<const char> input; // some larger string

while(input) { // forever :(
    std::size_t bytesRead = ...;
    input = input.suffix(bytesRead); // ... because suffix never results in a null view
}

That's not really intuitive, as e.g. std::istream also returns false when reaching EOF.


So, fixing the bool conversion like this (as partially done in the PR already) seems like a good idea (for me at least), but there's one problem -- the bool conversion operator is disabled on MSVC as it causes ambiguities for operator+, basically clashing with the pointer conversion operator, even though the bool conversion is explicit, due to implicit backwards compatibility with shit old C++ code (/permissive). So there are the following options:

  • Leave explicit operator bool() like it currently is (return _data;) and update the documentation to say it basically doesn't do what would one expect. That makes it consistent with MSVC, but practically useless and potentially dangerous if accidentally used for the wrong purpose.
  • FIx it, but since it's disabled on MSVC, have totally different behavior there, causing code to break in unexpected ways. Wrong solution.
  • Fix the ambiguity on MSVC by forcing /permissive- on everyone. Would mean dropping MSVC 2017 support, as it's known to be crashy and buggy in some cases.
  • Add an explicit branch to the constructor which sets the data pointer to nullptr in case size is zero. That makes it consistent with Array behavior, but might break some code. (I know about at least one case in the OpenDDL parse where I'm treating zero-sized nullptr view and zero-sized non-nullptr view differently -- the first one indicating a parse error, the second indicating EOF.) Not an option, since a lot code relies on a distinction between a null empty view and a non-null empty view.
  • Drop the implicit pointer conversion, thus fixing the ambiguity on MSVC.

Another possibility, in a different direction than this PR:

  • Return true only if both size is nonzero and pointer is non-null?

To ensure consistency, this applies also to the StaticArrayView, but there zero-sized views are problematic at the moment due to some non-ISO-standard zero-sized arrays being present in function signatures.

Does not apply to the new StringView classes, as there I deliberately don't have const char* conversion -- simply because not every view is null terminated and this would be a very convenient footgun.

Opinions? Thank you :)

@mosra
Copy link
Owner Author

mosra commented Jul 5, 2019

During a port to MSVC 2019 I discovered that the explicit bool conversion works properly with the /permissive- flag enabled. However it's not possible to detect presence of that flag and OTOH forcing the users to have this flag enabled might break 3rd party code that expects non-standard MSVC behavior.

They plan to make that flag enabled by default at some point (2038?) but until then we can only wait I'm afraid (and then five more years until we can drop support for compilers that don't have it enabled by default).

@mosra
Copy link
Owner Author

mosra commented Jan 20, 2020

Recently I'm thinking about removing the implicit pointer conversion, as it causes various problems when comparing containers in TestSuite (more often than not the common type of an Array and ArrayView is a pointer, which is wrong), and doesn't actually improve speed of element access in Debug builds (as it's either operator*() or operator[]() anyway). Plus with an operator[] we could do range-checking in debug builds (as the additional comparison overhead is minimal compared to the function call).

OTOH, I am not sure if the above is actually a good thing to do. For example, Directory::read() could be updated to distinguish better between a "file not found" state and "file found but empty" state. Right now it returns {nullptr, 0} in both, but it could return {nullptr, 0} when the file is not found and {somePointer, 0, customNoOpDeleter} when the file is found but empty. Then, the following would work with the current state (but would break if the bool conversion change is done): Not anymore, the new path functions will return a non-ambigous Optional that forces explicit failure handling instead of silently treating a failure the same as an empty file.

if(Containers::Optional<Containers::Array<char>> file = Utility::Path::readFile("foo.dat")) {
   // the file is found, but might be empty
}

This semantics makes much more sense (while the above can't really be fixed unless taking the file out of the loop and testing for file.data(), which makes it much more verbose). The above parser snippet could be easily fixed to the following:

ArrayView<const char> input; // some larger string

while(!input.empty()) { // we explicitly run for as long as there's something
    std::size_t bytesRead = ...;
    input = input.suffix(bytesRead);
}

@mosra mosra added this to the 2020.0a milestone Jan 20, 2020
@mosra mosra modified the milestones: 2020.0a, 2020.0b Jun 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

1 participant