Skip to content

Conversation

@wusatosi
Copy link
Member

@wusatosi wusatosi commented Jan 15, 2025

The goal of this PR is to add basic spec tests so the implementation would be minimally useable.

It would be fine to update the implementation itself in this branch.

See spec: https://eel.is/c++draft/inplace.vector

Meant to replace #46 which I accidentally broke.

cc: @jbab

@wusatosi wusatosi marked this pull request as draft January 15, 2025 22:25
@wusatosi wusatosi marked this pull request as ready for review January 19, 2025 04:26
@wusatosi
Copy link
Member Author

The only thing left should be [sequence.reqmts]

Copy link
Collaborator

@jbab jbab left a comment

Choose a reason for hiding this comment

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

Some remarks on constructor.test.cpp. I still need to go through the other files.

Comment on lines +28 to +36
if (std::is_same_v<T, NonTriviallyDefaultConstructible> ||
std::is_same_v<T, NonTrivial>) {

IV mid_correct;
for (auto i = 0ul; i < mid_size; ++i)
mid_correct.emplace_back();

EXPECT_EQ(mid, mid_correct);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

When I first read this I had to think a bit what this is for. It is for the the effect "... with n default-inserted elements". This means that the values are value-initialized (see https://en.cppreference.com/w/cpp/named_req/DefaultInsertable). Therefore there is no need to restrict the test to non trivially default constructible types. It should work with all default constructible types (all of the test types are). The test is that each element is equal to T{}.

Suggested change
if (std::is_same_v<T, NonTriviallyDefaultConstructible> ||
std::is_same_v<T, NonTrivial>) {
IV mid_correct;
for (auto i = 0ul; i < mid_size; ++i)
mid_correct.emplace_back();
EXPECT_EQ(mid, mid_correct);
}
// All elements of mid are T{}
EXPECT_EQ(std::count(mid.begin(), mid.end(), T{}), mid.size());

Copy link
Member

@JeffGarland JeffGarland Jan 19, 2025

Choose a reason for hiding this comment

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

We have this project now set to c++23 -- I'd suggest we simplify this code using ranges algo

EXPECT_EQ(std::ranges::count(mid, T{}), mid.size());

Alternatively std::ranges::all_of would work as well. Comment applies below as well

Copy link
Member Author

@wusatosi wusatosi Jan 20, 2025

Choose a reason for hiding this comment

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

The problem here is that types other than this two does not have defined value:
e.g.:

// A trivial type
struct Trivial {
  int value;
  friend constexpr bool operator==(Trivial x, Trivial y) = default;
};

The value is not defined for implicit default constructor. Equality check here is invalid and undefined.

Copy link
Member Author

Choose a reason for hiding this comment

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

Only NonTriviallyDefaultConstructible and NonTrivial have their member variables defined.

// A type which is not trivially default constructible (and thus not trivial),
// and all other SMFs are trivial.
struct NonTriviallyDefaultConstructible {
  int value = 0;
  friend constexpr bool operator==(NonTriviallyDefaultConstructible x, NonTriviallyDefaultConstructible y) = default;
};

Copy link
Member

Choose a reason for hiding this comment

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

The problem here is that types other than this two does not have defined value: e.g.:

// A trivial type
struct Trivial {
  int value;
  friend constexpr bool operator==(Trivial x, Trivial y) = default;
};

The value is not defined for implicit default constructor. Equality check here is invalid and undefined.

Make it defined is easy and this should be trivial?

struct Trivial {
   int value = 0;
   bool operator==( const Trivial& ) const = default;
};

https://godbolt.org/z/63nc3hqGr

Copy link
Member

Choose a reason for hiding this comment

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

Sure, so basically there's no testing this because anything trivial can't do any initialization and so any comparison will be UB -- no matter the technique used to compare.

Copy link
Member Author

Choose a reason for hiding this comment

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

Exactly

Copy link
Collaborator

Choose a reason for hiding this comment

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

@JeffGarland, @wusatosi you are right, I forgot about my own implementation of the test types, of course we need a condition here.
What bothers me a bit about the current condition

if (std::is_same_v<T, NonTriviallyDefaultConstructible> ||
  std::is_same_v<T, NonTrivial>) 

is that it mentions the test types directly. We may add to them or remove some of them in the future or rename them, so this becomes brittle.
So I'm suggesting to focus on the type properties which are relevant here. This is obviously

!std::is_trivially_default_constructible_v<T>

But there are also trivially default constructible types which can be value-initialised to a defined value (all zeros). These are the scalar types. So or condition becomes

if (std::is_scalar_v<T> || !std::is_trivially_default_constructible_v<T>) ...

Agree?

Copy link
Member Author

Choose a reason for hiding this comment

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

This makes much more sense, thank you so much for your input. I will apply your suggestion once I have time.

Copy link
Collaborator

Choose a reason for hiding this comment

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

In addition, if T is an aggregate type, the expression T{} performs aggregate initialization, which means that the member value will be initialized to zero. There are a few types in the test suite that qualify for this.

Comment on lines 89 to 101
TYPED_TEST(Constructors, CopyIter) {
// template<class InputIterator>
// constexpr inplace_vector(InputIterator first, InputIterator last);
// Effects: Constructs an inplace_vector equal to the range [first, last).
// Complexity: Linear in distance(first, last).

using IV = TestFixture::IV;

auto reference = this->unique();
IV device(reference.begin(), reference.end());

EXPECT_EQ(device, reference);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

What this fails to test is whether the constructor works with an iterator which is InputIterator and nothing else. I.e. single pass, going forward only.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah I see, is there any standard library facility to warp an existing iterator to make a single pass forward only iterator?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not that I'm aware of.

Copy link
Member Author

Choose a reason for hiding this comment

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

Alright noted.

}

{
auto mid = std::midpoint(0ul, reference.size());
Copy link
Member

Choose a reason for hiding this comment

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

0ul is preferably 0uz in c++23 to ensure it's size_t

Copy link
Member Author

Choose a reason for hiding this comment

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

thanks for the input!

Copy link
Member Author

Choose a reason for hiding this comment

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

I ended up reverting this, we would like to eventually aim to support C++20.

Copy link
Member

Choose a reason for hiding this comment

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

ok, but the assumption that 0ul == size_t may not hold everywhere. I realize it's verbose but I think std::size_t(0) would be more portable.

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right here

@wusatosi wusatosi mentioned this pull request Jan 20, 2025
This reverts commit 451f0c5.

This is because `uz` is a C++23 feature, we will eventually support
earlier versions
@wusatosi
Copy link
Member Author

Turns out C++20 support is doable with minimal changes, I will update the docs in another PR but set the checks here for C++20.

@wusatosi wusatosi merged commit 5340a03 into main Jan 24, 2025
32 checks passed
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.

4 participants