Skip to content

Commit 698e566

Browse files
committed
Added some examples of what metaclass generation might look like
1 parent 4b0a3e3 commit 698e566

11 files changed

+312
-1
lines changed

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ endif()
1313
enable_testing()
1414
add_subdirectory(tests)
1515
add_subdirectory(include/scl)
16-
add_subdirectory(include/scl/utils)
16+
add_subdirectory(include/scl/utils)
17+
add_subdirectory(include/scl/meta)

include/scl/meta/CMakeLists.txt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
set(CMAKE_CXX_STANDARD 23)
2+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
3+
message(STATUS META)
4+
include(../../../cmake/icm_build_failure_testing.cmake)
5+
include(../../../cmake/icm_testing.cmake)
6+
7+
# Global include for icm based tests
8+
include_directories("../../include")
9+
10+
#if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
11+
# OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
12+
# set (ERROR_STRING "does not satisfy 'send'")
13+
#elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
14+
# set (ERROR_STRING "constraints not satisfied")
15+
#elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
16+
# # using Visual Studio C++
17+
#endif()
18+
19+
icm_glob_build_failure_tests(
20+
PATTERN build_fail_*
21+
ERROR_MATCHES "error: "
22+
)
23+
24+
file(GLOB test_files LIST_DIRECTORIES false "${CMAKE_CURRENT_SOURCE_DIR}" *.test.cpp)
25+
list(FILTER test_files EXCLUDE REGEX "build_")
26+
message(test_files: ${test_files})
27+
28+
foreach(test_file ${test_files})
29+
get_filename_component(name_without_extension "${test_file}" NAME_WE)
30+
icm_add_test(
31+
NAME ${name_without_extension}
32+
SOURCES ${test_file})
33+
34+
if(${test_file} MATCHES fail_*)
35+
set_tests_properties(${name_without_extension} PROPERTIES
36+
WILL_FAIL TRUE)
37+
endif()
38+
endforeach()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//
2+
// Created by David Rowland on 11/02/2025.
3+
//
4+
5+
#include "cow_vector.h"
6+
7+
int main()
8+
{
9+
auto vec = cow_vector<int>();
10+
[[maybe_unused]] auto vec_p = &vec;
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//
2+
// Created by David Rowland on 11/02/2025.
3+
//
4+
5+
#include "cow_vector.h"
6+
7+
int main()
8+
{
9+
[[maybe_unused]] auto vec = std::make_unique<cow_vector<int>>();
10+
}

include/scl/meta/cow_vector.h

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//
2+
// Created by David Rowland on 17/06/2025.
3+
//
4+
5+
#pragma once
6+
7+
#define DATA_RACE_DETECTED { std::println ("ERROR: data race detected"); std::exit(1); }
8+
#include "../utils/data_race_checker.test.h"
9+
10+
void check(bool x)
11+
{
12+
assert (x);
13+
14+
if (! x)
15+
std::exit(1);
16+
}
17+
18+
19+
template<typename T>
20+
class cow_vector
21+
{
22+
public:
23+
cow_vector()
24+
: data (std::make_shared<test_vector<T>>())
25+
{}
26+
27+
cow_vector (const cow_vector& o) {
28+
scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (std::addressof (o)));
29+
data = o.data;
30+
}
31+
32+
cow_vector& operator= (const cow_vector& o) {
33+
scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (std::addressof (o)));
34+
data = o.data;
35+
return *this;
36+
}
37+
38+
void clear() {
39+
scl::scoped_check<scl::check_type::write> _ (scl::data_race_registry<>::get_state (this));
40+
copy_if_shared();
41+
data->clear();
42+
}
43+
44+
void push_back(const T& value) {
45+
scl::scoped_check<scl::check_type::write> _ (scl::data_race_registry<>::get_state (this));
46+
copy_if_shared();
47+
data->push_back(value);
48+
}
49+
50+
T& back() {
51+
scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (this));
52+
return data->back();
53+
}
54+
55+
size_t size() const {
56+
scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (this));
57+
return data->size();
58+
}
59+
60+
bool empty() const {
61+
scl::scoped_check<scl::check_type::read> _ (scl::data_race_registry<>::get_state (this));
62+
return data->empty();
63+
}
64+
65+
// Prevents dynamic allocation
66+
void* operator new(size_t) = delete;
67+
void* operator new[](size_t) = delete;
68+
69+
// Prevents taking the address
70+
cow_vector<T>* operator&() = delete;
71+
const cow_vector<T>* operator&() const = delete;
72+
73+
class inout
74+
{
75+
public:
76+
~inout()
77+
{
78+
*cow_vector_arg = cow_vector; // copy back modifications
79+
}
80+
81+
void push_back(const T& value) {
82+
cow_vector.push_back(value);
83+
}
84+
85+
// reflect to generate and forward all
86+
// functions to internal copy
87+
88+
private:
89+
friend class cow_vector;
90+
cow_vector* cow_vector_arg; // pointer to original
91+
cow_vector cow_vector; // copy, safe to modify
92+
93+
inout (class cow_vector* v)
94+
: cow_vector_arg (v),
95+
cow_vector (*v)
96+
{}
97+
};
98+
99+
inout make_inout()
100+
{
101+
return inout (this);
102+
}
103+
104+
private:
105+
void copy_if_shared() {
106+
scl::scoped_check<scl::check_type::write> _ (scl::data_race_registry<>::get_state (this));
107+
if (data.use_count() > 1) {
108+
data = std::make_shared<test_vector<T>> (*data);
109+
}
110+
}
111+
112+
std::shared_ptr<test_vector<T>> data;
113+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// Created by David Rowland on 11/02/2025.
3+
//
4+
5+
#include "cow_vector.h"
6+
#include <latch>
7+
8+
using namespace std::literals;
9+
10+
void push_42 (cow_vector<int>* v) {
11+
std::latch latch (1);
12+
std::thread t ([v, &latch] mutable
13+
{
14+
latch.count_down();
15+
auto vec = *v;
16+
[[maybe_unused]] auto vec2 = vec;
17+
});
18+
19+
latch.wait();
20+
v->push_back (42);
21+
22+
t.join();
23+
}
24+
25+
int main()
26+
{
27+
for (;;)
28+
{
29+
cow_vector<int> vec;
30+
vec.push_back (1);
31+
32+
push_42 (std::addressof (vec));
33+
check(vec.back() == 42);
34+
}
35+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// Created by David Rowland on 11/02/2025.
3+
//
4+
5+
#include "cow_vector.h"
6+
7+
int main()
8+
{
9+
cow_vector<int> vec;
10+
vec.push_back (1);
11+
vec.push_back (2);
12+
vec.push_back (3);
13+
14+
check(vec.size() == 3uz);
15+
16+
[vec2 = vec] mutable {
17+
vec2.push_back (42);
18+
check(vec2.size() == 4uz);
19+
20+
vec2.push_back (43);
21+
check(vec2.size() == 5uz);
22+
23+
vec2.push_back (44);
24+
check(vec2.size() == 6uz);
25+
}();
26+
27+
check(vec.size() == 3uz);
28+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// Created by David Rowland on 11/02/2025.
3+
//
4+
5+
#include "cow_vector.h"
6+
#include <latch>
7+
8+
void push_42 (cow_vector<int>::inout v)
9+
{
10+
std::latch latch (1);
11+
std::thread t ([v, &latch] mutable
12+
{
13+
latch.count_down();
14+
auto vec = v;
15+
[[maybe_unused]] auto vec2 = vec;
16+
});
17+
18+
latch.wait();
19+
v.push_back (42);
20+
21+
t.join();
22+
}
23+
24+
int main()
25+
{
26+
for (int i = 0; i < 100'000; ++i)
27+
{
28+
cow_vector<int> vec;
29+
vec.push_back (1);
30+
31+
push_42 (vec.make_inout());
32+
check(vec.back() == 42);
33+
}
34+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// Created by David Rowland on 11/02/2025.
3+
//
4+
5+
#include "cow_vector.h"
6+
#include <latch>
7+
8+
void push_42 (cow_vector<int> v) {
9+
v.push_back (42);
10+
}
11+
12+
int main()
13+
{
14+
using namespace std::literals;
15+
16+
for (int i = 0; i < 100'000; ++i)
17+
{
18+
cow_vector<int> vec;
19+
20+
std::latch latch (1);
21+
std::thread t ([vec, &latch] mutable
22+
{
23+
latch.count_down();
24+
vec.push_back(43);
25+
check(vec.back() == 43);
26+
});
27+
28+
latch.wait();
29+
30+
push_42 (vec);
31+
check(vec.empty());
32+
33+
t.join();
34+
}
35+
}

include/scl/utils/data_race_checker.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
* is quicker than getting the thread ID. A rudimentary benchmark (below) shows
4747
* getting this is the same on clang 17 and GCC 13.2:
4848
* https://quick-bench.com/q/iTnk1X3mGYUQ2yryuK_Wcg6JJ6Q
49+
* https://quick-bench.com/q/NdoJqZN3jp93lHbuxmbLg9xZUJU
4950
*
5051
* When detecting if there is a data-race, comparing thread IDs is very quick. I
5152
* would expect finding if two addresses are on the same stack is more costly as

0 commit comments

Comments
 (0)