Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
!*.view/
*.user
*.user.*
thirtparty/*/
thirdparty/*/
.vagrant/
.vscode/
.vs/
8 changes: 7 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,15 @@ Fold & indexOf

## License

This project usses the MIT License - see LICENSE file.
This project uses the MIT License - see LICENSE file.

## Contributions

Please report your ideas and bugs as issues!
Pull requests are also welcome.

## Development

Checkout the repository, then run the `git_clone_googletest.sh` from within the folder *thirdparty* to checkout the required google test framework.

The BasiC idea behind the libary part is, that each component can be used standalone. Optional functionality can be introduced by including the dedicated header file.
3 changes: 2 additions & 1 deletion src/meta17.lib/meta17/align.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ namespace meta17 {

template<size_t Align, class O>
constexpr auto alignOffset(O offset, Const<Align> = {}) -> O {
return 0 == offset * Align ? offset : (((offset - 1) / Align) + 1) * Align;
// MSVC needs the static_cast in some cases where O = size_t, clang does not. Compiler bug? #workaround 1
return 0 == offset * Align ? offset : static_cast<O>((((offset - 1) / Align) + 1) * Align);
}

template<size_t Offset, size_t Align>
Expand Down
27 changes: 17 additions & 10 deletions src/partial17.lib/partial17/Partial.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,13 @@ struct Partial {

auto bitset() const -> std::bitset<max_count> { return m_bits; }

auto count() const {
auto acc = size_t{};
visitAll([&](auto&) { acc++; });
return acc;
}
auto countInitialized() const { return m_bits.count(); }

auto countAll() const { return m_bits.size(); }

auto size() const {
auto acc = size_t{};
visitAll([&](auto& v) {
visitInitialized([&](auto& v) {
using T = decltype(v);
acc = alignOffset<alignof(T)>(acc) + sizeof(v);
});
Expand Down Expand Up @@ -195,13 +193,22 @@ struct Partial {
return get<index>();
}

template<class F>
auto visitInitialized(F&& f) {
visitInitializedInternal(std::forward<F>(f), m_data.get());
}
template<class F>
auto visitInitialized(F&& f) const {
visitInitializedInternal(std::forward<F>(f), m_data.get());
}

template<class F>
auto visitAll(F&& f) {
visitInitialized(std::forward<F>(f), m_data.get());
visitIndexTypes(std::forward<F>(f), Indices{});
}
template<class F>
auto visitAll(F&& f) const {
visitInitialized(std::forward<F>(f), m_data.get());
visitIndexTypes(std::forward<F>(f), Indices{});
}

[[nodiscard]] auto merge(const Partial& o) const -> Partial {
Expand All @@ -212,7 +219,7 @@ struct Partial {

private:
void destructAll() {
visitAll([](auto& v) {
visitInitialized([](auto& v) {
using T = std::remove_reference_t<decltype(v)>;
v.~T();
});
Expand All @@ -234,7 +241,7 @@ struct Partial {
}

template<class F, class Ptr>
auto visitInitialized(F&& f, Ptr* ptr) const {
auto visitInitializedInternal(F&& f, Ptr* ptr) const {
ptr = alignPointer<max_align>(ptr);
visitIndexTypes(
[&](auto index, auto type) {
Expand Down
20 changes: 20 additions & 0 deletions src/partial17.lib/partial17/Partial.make.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "Partial.h"

#include "Partial.make.h"
#include "Partial.trait.h"

#include <meta17/Const.ops.h>
#include <meta17/Type.ops.h>

#include <gtest/gtest.h>

using namespace partial17;

using meta17::type;

TEST(Partial, make) {
using PM = MakePartial<TypePack<char, int, float>>;
using PC = Partial<char, int, float>;

static_assert(type<PM> == type<PC>);
}
20 changes: 20 additions & 0 deletions src/partial17.lib/partial17/Partial.ops.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once
#include "Partial.h"
#include <iostream>
namespace partial17 {

template<class... Ts>
bool constexpr operator==(const Partial<Ts...>& a, const Partial<Ts...>& b) {
if (a.bitset() != b.bitset()) return false;

bool equal = true;
a.visitAll([&](auto i, auto) { equal &= (!a.has(i) || a.get(i) == b.get(i)); });
return equal;
}

template<class... Ts>
bool constexpr operator!=(const Partial<Ts...>& a, const Partial<Ts...>& b) {
return !(a == b);
}

} // namespace partial17
31 changes: 31 additions & 0 deletions src/partial17.lib/partial17/Partial.ops.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#include "Partial.ops.h"

#include <gtest/gtest.h>

using namespace partial17;

TEST(Partial, ops) {
// using P1 = Partial<char, int, float, bool>;
// using P2 = Partial<bool, float, int, char>;

// auto p1 = P1{'c', 2.3f};
// auto p2 = P2{'c', 2.3f};
// EXPECT_EQ(p1, p2);

using P = Partial<char, int, float, bool>;
const P p1{3.2f};
const P p2{3.2f};
EXPECT_EQ(p1, p2);

const P p3{3.1f};
const P p4{3.3f};
EXPECT_NE(p3, p4);

const P p5{99};
const P p6{'c'};
EXPECT_NE(p5, p6);

const P p7{'c', 3.2f};
const P p8{3.2f};
EXPECT_NE(p7, p8);
}
22 changes: 22 additions & 0 deletions src/partial17.lib/partial17/Partial.ostream.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once
#include "Partial.h"

#include <ostream>

namespace partial17 {

template<class Chr, class Traits, class... Ts>
auto operator<<(std::basic_ostream<Chr, Traits>& out, const Partial<Ts...>& t) -> decltype(out)& {
out << "<[";
bool first = true;
t.visitInitialized([&](auto& v) {
if (first)
first = false;
else
out << "; ";
out << v;
});
return out << "]>";
}

} // namespace partial17
15 changes: 15 additions & 0 deletions src/partial17.lib/partial17/Partial.ostream.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include "Partial.ostream.h"

#include <gtest/gtest.h>
#include <sstream>

using namespace partial17;

TEST(Partial, ostream) {
using P = Partial<int, double, char, short, bool>;
const P p1{1, 'c', true}, p2{2.4, static_cast<short>(2)}; // see #workaround 1
std::stringstream actual, expected;
actual << p1 << " " << p2;
expected << "<[1; c; 1]> <[2.4; 2]>";
EXPECT_EQ(actual.str(), expected.str());
}
153 changes: 123 additions & 30 deletions src/partial17.lib/partial17/Partial.test.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "Partial.h"

#include "Partial.make.h"
#include "Partial.ops.h"
#include "Partial.trait.h"

#include <meta17/Const.ops.h> // index == index
Expand All @@ -11,16 +12,48 @@ using namespace partial17;

using meta17::type;

TEST(Partial, basic) {
auto x = Partial<char, int, float>{}; //
auto y = x; // copy constructor
x = y; // copy operator
x = std::move(y); // move operator
auto z = Partial<char, int, float>{std::move(x)}; // move construct
TEST(Partial, construction) {
using P = Partial<char, int, float>;
auto p0 = P{};
ASSERT_FALSE(p0.has<0>());
ASSERT_FALSE(p0.has<1>());
ASSERT_FALSE(p0.has<2>());
// TODO CK: Should this be possible? - crashes, would suggest a graceful value for production
Copy link
Member

Choose a reason for hiding this comment

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

This is by intention. get<N> is like a cast. You have to know that the type is there, otherwise ghosts will hunt you at night. If we throw exceptions here, the resulting program would suffer a huge performance hit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there no way to prevent an memory access crash when trying to read uninitialized memory? Since there is the m_bitset to check, maybe we can return a reference to some static default for production mode while crashing with an assertion in debug mode?

Copy link
Member

Choose a reason for hiding this comment

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

Checking every time kills the optimizer.
The solution would be to rename get into cast or as and add a get that is checked. Like operator[] and at on a std::vector. That way we have options at the usage side.

// EXPECT_EQ(p0.get<0>(), '\0');
// EXPECT_EQ(p0.get<1>(), 0);
// EXPECT_EQ(p0.get<2>(), 0);

auto p1 = P{'c', 2.3f};
auto p2 = p1; // copy constructor
auto p3 = P{std::move(p2)}; // move construct

EXPECT_EQ(p1.get<0>(), 'c');
EXPECT_EQ(p1.get<2>(), 2.3f);
EXPECT_EQ(p1.countInitialized(), 2ul);
EXPECT_EQ(p1.countAll(), 3ul);
// TODO CK: Consider alignment - does this need to consider the size of the bitset
Copy link
Member

Choose a reason for hiding this comment

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

size() is simply the size of the heap allocated for the partial. We might want to document this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since size() it returns a binary size, I would suggest changing the naming to something in that direction. The STL uses size() for the number of elements within a container (which is currently retrievend by countAll() and countInitialized()).

Copy link
Member

Choose a reason for hiding this comment

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

Yes, you are correct in your observation. It's best compared to capacity on a std::vector but the semantics are different. It seems the STL has the wrong names here. So I am quite undecided what is best. We need a wording scheme for these methods, document it and use it on all value types where appropriate.

EXPECT_EQ(p1.size(), sizeof(int) + sizeof(float));
EXPECT_EQ(p3.countInitialized(), 2ul);
EXPECT_EQ(p3.countAll(), 3ul);
EXPECT_EQ(p1.size(), sizeof(int) + sizeof(float));
EXPECT_EQ(p1, p3);
}

TEST(Partial, assignment) {
using P = Partial<char, int, float>;
auto p0 = P{'c', 2.3f};
auto p1 = P{};
p1 = p0; // copy operator
auto p2 = P{};
p2 = std::move(p1); // move operator

EXPECT_EQ(p0.get<0>(), 'c');
EXPECT_EQ(p0.get<2>(), 2.3f);
EXPECT_EQ(p0, p2);
}

TEST(Partial, fromFactory) {
const auto created = Partial<char, int, float>::fromFactory(
const auto p = Partial<char, int, float>::fromFactory(
[](size_t i) { return (0 == i % 2); },
[](auto i) {
if constexpr (i == index<0>) {
Expand All @@ -33,36 +66,96 @@ TEST(Partial, fromFactory) {
return 3.14f;
}
});
ASSERT_TRUE(created.has<0>());
ASSERT_FALSE(created.has<1>());
ASSERT_TRUE(created.has<2>());
ASSERT_TRUE(p.has<0>());
ASSERT_FALSE(p.has<1>());
ASSERT_TRUE(p.has<2>());

EXPECT_EQ(created.get<0>(), 'a');
EXPECT_EQ(created.get<2>(), 3.14f);
EXPECT_EQ(p.get<0>(), 'a');
EXPECT_EQ(p.get<2>(), 3.14f);
}

auto copy = created;
ASSERT_TRUE(copy.has<0>());
ASSERT_FALSE(copy.has<1>());
ASSERT_TRUE(copy.has<2>());
TEST(Partial, type) {
auto p = Partial<char, int, float>{'\x23', 23};
ASSERT_TRUE(p.has(type<char>));
ASSERT_TRUE(p.has(0));
EXPECT_EQ(0x23, p.get(type<char>));
EXPECT_EQ(0x23, p.get(index<0>));
ASSERT_TRUE(p.has(type<int>));
ASSERT_TRUE(p.has(1));
ASSERT_EQ(23, p.get(type<int>));
EXPECT_EQ(23, p.get(index<1>));

EXPECT_EQ(copy.get<0>(), 'a');
EXPECT_EQ(copy.get<2>(), 3.14f);
// TODO CK: Does prevent valid compilation? Microsoft Visual C++ BUG?
// auto m = Partial<int, int>{23, 32};
// not allowed due to used TypePack
// ASSERT_TRUE(m.has(type<int>));
// ASSERT_EQ(23, m.get(type<int>));
}

TEST(Partial, fromArgs) {
const auto created = Partial<char, int, float>(3.14f, 'a'); //
ASSERT_TRUE(created.has<0>());
ASSERT_FALSE(created.has<1>());
ASSERT_TRUE(created.has<2>());
TEST(Partial, merge) {
// Disjunct merge
using P = Partial<char, int, float>;
auto p0 = P{'\x23'};
auto p1 = P{2.3f};
auto p2 = p0.merge(p1);
ASSERT_TRUE(p2.has<0>());
ASSERT_FALSE(p2.has<1>());
ASSERT_TRUE(p2.has<2>());

EXPECT_EQ(p2.get<0>(), '\x23');
EXPECT_EQ(p2.get<2>(), 2.3f);

// Overwrite merge
auto p3 = P{2};
auto p4 = P{4};
auto p5 = p3.merge(p4);
ASSERT_FALSE(p5.has<0>());
ASSERT_TRUE(p5.has<1>());
ASSERT_FALSE(p5.has<2>());

EXPECT_EQ(created.get<0>(), 'a');
EXPECT_EQ(created.get<2>(), 3.14f);
EXPECT_EQ(p5.get<1>(), 4);

auto p6 = p4.merge(p3);
ASSERT_FALSE(p6.has<0>());
ASSERT_TRUE(p6.has<1>());
ASSERT_FALSE(p6.has<2>());

EXPECT_EQ(p6.get<1>(), 2);
}

TEST(Partial, byType) {
TEST(Partial, visitAll) {
using P = Partial<char, int, float>;
auto p = P{'\x23'};

ASSERT_EQ(p.countInitialized(), 1ul);
ASSERT_EQ(p.countAll(), 3ul);

size_t count = 0;
p.visitAll([&](auto i, auto t) {
++count;
if (i == 0) {
EXPECT_TRUE(p.has(i));
EXPECT_EQ(t, type<char>);
}
else {
EXPECT_FALSE(p.has(i));
EXPECT_NE(t, type<char>);
}
});
EXPECT_EQ(count, p.countAll());
}

TEST(Partial, visitInitialized) {
using P = Partial<char, int, float>;
auto p = P{'\x23', 2.3f};

ASSERT_EQ(p.countInitialized(), 2ul);
ASSERT_EQ(p.countAll(), 3ul);

auto x = Partial<char, int, float>{'\x23'}; //
if (x.has(type<char>)) {
ASSERT_EQ(35, x.get(type<char>));
}
size_t count = 0;
p.visitInitialized([&](auto v) {
++count;
EXPECT_TRUE(type<decltype(v)> == type<char> || type<decltype(v)> == type<float>);
});
EXPECT_EQ(count, p.countInitialized());
}
Loading