Skip to content

Commit 8c0d1db

Browse files
committed
Changes User id to be it's own type as a template, allowing for constexpr pre C++20 in most cases
1 parent 50edf03 commit 8c0d1db

9 files changed

Lines changed: 135 additions & 58 deletions

File tree

.github/workflows/cmake.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
4040
run: |
4141
if [ ${{ matrix.os }} == 'windows-latest' ]; then
42-
cmake -DTEST=ON -Dgtest_disable_pthreads=ON -B ${{github.workspace}}/build
42+
cmake -DTEST=ON -Dgtest_disable_pthreads=ON -B ./build
4343
else
4444
cmake -DTEST=ON -B ${{github.workspace}}/build
4545
fi;

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@ packaging/
3939
.vscode
4040
.code-workspace
4141
CXXGraph.code-workspace
42+
# ignore Visual Studio files
43+
/.vs

include/CXXGraph/Edge/DirectedWeightedEdge_decl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class DirectedWeightedEdge : public DirectedEdge<T>, public Weighted {
7070
Weighted::getWeight());
7171
}
7272

73-
friend std::ostream &operator<< <>(std::ostream &os,
73+
friend std::ostream &operator<< (std::ostream &os,
7474
const DirectedWeightedEdge<T> &edge);
7575
};
7676

include/CXXGraph/Edge/Weighted_decl.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ class Weighted {
2828
double weight = 0.0;
2929

3030
public:
31-
Weighted();
32-
explicit Weighted(const double weight);
33-
virtual ~Weighted() = default;
34-
double getWeight() const;
35-
void setWeight(const double weight);
31+
constexpr Weighted() noexcept;
32+
constexpr explicit Weighted(const double weight) noexcept;
33+
virtual ~Weighted() noexcept = default;
34+
constexpr double getWeight() const;
35+
constexpr void setWeight(const double weight);
3636
};
3737
} // namespace CXXGraph
3838

include/CXXGraph/Edge/Weighted_impl.hpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,19 @@
2727
namespace CXXGraph {
2828

2929
// inline because the implementation of non-template function in header file
30-
inline Weighted::Weighted() { weight = 0.0; }
30+
constexpr Weighted::Weighted() noexcept : weight(0.0) {}
3131

3232
// inline because the implementation of non-template function in header file
33-
inline Weighted::Weighted(const double weight) { this->weight = weight; }
33+
constexpr Weighted::Weighted(const double inputWeight) noexcept
34+
: weight(inputWeight) {}
3435

3536
// inline because the implementation of non-template function in header file
36-
inline double Weighted::getWeight() const { return weight; }
37+
constexpr double Weighted::getWeight() const noexcept { return weight; }
3738

3839
// inline because the implementation of non-template function in header file
39-
inline void Weighted::setWeight(const double weight) { this->weight = weight; }
40+
constexpr void Weighted::setWeight(const double weight) {
41+
this->weight = weight;
42+
}
4043

4144
} // namespace CXXGraph
4245

include/CXXGraph/Node/Node_decl.h

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,38 +22,38 @@
2222

2323
#pragma once
2424
#include <iostream>
25-
25+
#include <type_traits>
2626
#include "CXXGraph/Utility/id_t.hpp"
2727

2828
namespace CXXGraph {
29-
template <typename T>
29+
template <typename T, typename = std::string>
3030
class Node;
31-
template <typename T>
32-
std::ostream &operator<<(std::ostream &os, const Node<T> &node);
33-
template <typename T>
31+
template <typename T, typename UserID>
32+
std::ostream &operator<<(std::ostream &os, const Node<T, UserID> &node);
33+
template <typename T, typename UserID>
3434
class Node {
3535
private:
3636
CXXGraph::id_t id = 0;
37-
std::string userId = "";
37+
UserID userId{};
3838
T data;
39-
void setId(const std::string &);
39+
constexpr void setId(const UserID &);
4040

4141
public:
4242
using Node_t = T;
4343

44-
Node(const std::string &, const T &data);
44+
constexpr Node(const UserID &, const T &data);
4545
// Move constructor
46-
explicit Node(const std::string &, T &&data) noexcept;
47-
~Node() = default;
46+
constexpr explicit Node(const UserID &, T &&data) noexcept(std::is_nothrow_move_assignable<T>::value);
47+
~Node() noexcept = default;
4848
constexpr const CXXGraph::id_t &getId() const;
49-
const std::string &getUserId() const;
49+
constexpr const UserID &getUserId() const;
5050
constexpr const T &getData() const;
51-
void setData(T &&new_data);
51+
constexpr void setData(T &&new_data);
5252
// operator
53-
constexpr bool operator==(const Node<T> &b) const;
54-
constexpr bool operator<(const Node<T> &b) const;
53+
constexpr bool operator==(const Node<T, UserID> &b) const;
54+
constexpr bool operator<(const Node<T, UserID> &b) const;
5555

56-
friend std::ostream &operator<< <>(std::ostream &os, const Node<T> &node);
56+
friend std::ostream &operator<< <>(std::ostream &os, const Node<T, UserID> &node);
5757
};
5858

5959
} // namespace CXXGraph

include/CXXGraph/Node/Node_impl.hpp

Lines changed: 77 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,71 +20,117 @@
2020
#ifndef __CXXGRAPH_NODE_IMPL_H__
2121
#define __CXXGRAPH_NODE_IMPL_H__
2222

23+
#if __cplusplus >= 202004L
24+
#include <bitset>
25+
#else
26+
#include <cstring>
27+
#endif
28+
2329
#include <iomanip>
30+
#include <limits>
31+
#include <type_traits>
2432

2533
#include "Node_decl.h"
2634

2735
namespace CXXGraph {
36+
// internals not intended to be accessed by the end user
37+
namespace internals {
38+
template <typename AnyUserID>
39+
constexpr CXXGraph::id_t UserIdToId(const AnyUserID &id) {
40+
// in C++ 23 we have constexpr hashes
41+
if constexpr (__cplusplus >= 202302L) return std::hash<AnyUserID>{}(id);
42+
// hashing is trivial for integral types
43+
if constexpr (std::is_integral<AnyUserID>::value) {
44+
// assert nothing is greater than a size_t to avoid issues with GCC and
45+
// clang's 128 bit ints, a size_t is usually 64 bits
46+
static_assert(std::numeric_limits<CXXGraph::id_t>::max() >=
47+
std::numeric_limits<AnyUserID>::max());
48+
49+
if constexpr (std::is_signed<AnyUserID>::value) {
50+
// Add id to half max so that we have something unique
51+
return (std::numeric_limits<CXXGraph::id_t>::max() / 2) + id;
52+
}
53+
return static_cast<CXXGraph::id_t>(id);
54+
}
55+
// typepun the float into an int
56+
if constexpr (std::is_floating_point<AnyUserID>::value) {
57+
// static assert to avoid long double (80 bits rather than 64)
58+
static_assert(sizeof(CXXGraph::id_t) >= sizeof(AnyUserID));
59+
// In C++20 we can do typepunning at compile time
60+
#if __cplusplus >= 202004L
61+
return std::bit_cast<CXXGraph::id_t>(id);
62+
#else // < C++20
63+
// otherwise use memcpy (not compile time)
64+
CXXGraph::id_t result{};
65+
std::memcpy(&result, &id, sizeof(id));
66+
return result;
67+
#endif // >= C++20
68+
}
69+
// resort back to runtime hash
70+
return std::hash<AnyUserID>{}(id);
71+
}
72+
} // namespace internals
2873

29-
template <typename T>
74+
template <typename T, typename UserID>
3075
class Node;
3176

32-
template <typename T>
33-
Node<T>::Node(const std::string &id, const T &data) {
34-
this->userId = id;
35-
// the userid is set as sha512 hash of the user provided id
36-
setId(id);
37-
this->data = data;
77+
template <typename T, typename UserID>
78+
constexpr Node<T, UserID>::Node(const UserID &otherId, const T &otherData)
79+
: id(internals::UserIdToId(otherId)), userId(otherId), data(otherData) {
80+
static_assert(
81+
!std::is_pointer<UserID>::value,
82+
"Pointer is not allowed as an ID type, did you mean std::string?");
3883
}
3984

40-
template <typename T>
41-
Node<T>::Node(const std::string &id, T &&data) noexcept {
42-
this->userId = id;
43-
// the userid is set as sha512 hash of the user provided id
44-
setId(id);
45-
this->data = std::move(data);
85+
template <typename T, typename UserID>
86+
constexpr Node<T, UserID>::Node(const UserID &otherId, T &&otherData) noexcept(
87+
std::is_nothrow_move_assignable<T>::value)
88+
: id(internals::UserIdToId(otherId)), userId(otherId), data(otherData) {
89+
static_assert(
90+
!std::is_pointer<UserID>::value,
91+
"Pointer is not allowed as an ID type, did you mean std::string?");
4692
}
4793

48-
template <typename T>
49-
void Node<T>::setId(const std::string &inpId) {
50-
this->id = std::hash<std::string>{}(inpId);
94+
template <typename T, typename UserID>
95+
constexpr void Node<T, UserID>::setId(const UserID &inputId) {
96+
this->id = UserIdToId(inputId);
5197
}
5298

53-
template <typename T>
54-
constexpr const CXXGraph::id_t &Node<T>::getId() const {
99+
template <typename T, typename UserID>
100+
constexpr const CXXGraph::id_t &Node<T, UserID>::getId() const {
55101
return id;
56102
}
57103

58-
template <typename T>
59-
const std::string &Node<T>::getUserId() const {
104+
template <typename T, typename UserID>
105+
constexpr const UserID &Node<T, UserID>::getUserId() const {
60106
return userId;
61107
}
62108

63-
template <typename T>
64-
constexpr const T &Node<T>::getData() const {
109+
template <typename T, typename UserID>
110+
constexpr const T &Node<T, UserID>::getData() const {
65111
return data;
66112
}
67113

68-
template <typename T>
69-
void Node<T>::setData(T &&new_data) {
70-
this->data = std::move(new_data);
114+
template <typename T, typename UserID>
115+
constexpr void Node<T, UserID>::setData(T &&newData) {
116+
this->data = std::move(newData);
71117
}
72118

73119
// The data type T must have an overload of the equality operator
74-
template <typename T>
75-
constexpr bool Node<T>::operator==(const Node<T> &b) const {
120+
template <typename T, typename UserID>
121+
constexpr bool Node<T, UserID>::operator==(const Node<T, UserID> &b) const {
76122
return (this->id == b.id && this->data == b.data);
77123
}
78124

79-
template <typename T>
80-
constexpr bool Node<T>::operator<(const Node<T> &b) const {
125+
template <typename T, typename UserID>
126+
constexpr bool Node<T, UserID>::operator<(const Node<T, UserID> &b) const {
81127
return (this->id < b.id);
82128
}
83129

84130
// ostream overload
85131
// The data type T must have an overload of the ostream operator
86-
template <typename T>
87-
std::ostream &operator<<(std::ostream &os, const Node<T> &node) {
132+
template <typename T, typename UserID>
133+
std::ostream &operator<<(std::ostream &os, const Node<T, UserID> &node) {
88134
os << "Node: {\n"
89135
<< " Id:\t" << node.userId << "\n Data:\t" << node.data << "\n}";
90136
return os;

include/CXXGraph/Utility/Typedef.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ namespace CXXGraph {
3636
template <typename T>
3737
using shared = std::shared_ptr<T>;
3838

39-
template <typename T>
39+
template <typename T, typename UserID>
4040
class Node;
4141

4242
template <typename T>

test/ConstexprTests.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
2+
#include <memory>
3+
4+
#include "CXXGraph/CXXGraph.hpp"
5+
#include "gtest/gtest.h"
6+
7+
// Boolean results will always be true
8+
// functions are only here to test that this compiles
9+
constexpr bool IsConstexprContextCreateable() {
10+
CXXGraph::Node<int, size_t> node1(1, 1);
11+
return true;
12+
}
13+
14+
constexpr bool IsConstexprCreateable() {
15+
constexpr CXXGraph::Node<int, size_t> node1(1, 1);
16+
return true;
17+
}
18+
19+
20+
TEST(ConstexprTests, IsConstexprContextCreateable) {
21+
ASSERT_TRUE(IsConstexprContextCreateable());
22+
}
23+
24+
TEST(ConstexprTests, IsConstexprCreateable) {
25+
ASSERT_TRUE(IsConstexprCreateable());
26+
}

0 commit comments

Comments
 (0)