Skip to content

Commit 7dd8886

Browse files
authored
Merge pull request #98 from Flow-IPC/open_tkt95_cxx20_support
C++20 mode build and Github-CI-pipline support. C++17 remains the main/minimal mode, but we now ensure user can build/use in C++20 mode also.
2 parents 32f0806 + 5fae655 commit 7dd8886

File tree

8 files changed

+203
-47
lines changed

8 files changed

+203
-47
lines changed

.github/workflows/main.yml

Lines changed: 129 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,44 @@ jobs:
139139
140140
strategy:
141141
fail-fast: false
142+
143+
# Our basic matrix is compiler-version x build-test-config. The latter is the usual possibilities namely
144+
# Debug, Release, ..., plus a few runtime-sanitizer-oriented variations. Generally we want to run the entire
145+
# cross-product AxB of possibilities; but for various reasons we exclude some specific combinations from the
146+
# matrix. So far, then: specify full matrix, specify certain combos to remove from it.
147+
#
148+
# There is however another variable: the C++ standard to specify. In actual practice -- as explained in
149+
# FlowLikeCodeGenerate.cmake, but we *very* briefly recap -- the min required standard is 17, and it is indeed
150+
# *the* standard we use, never taking advantage (via conditional compilation, etc.) of 20-or-higher. So,
151+
# the main matrix shall built with std=17. In addition, though, we *allow* C++20 or higher. In practice, 99.999%
152+
# C++17 code shall build equivalently with C++20 (as for C++23, we haven't looked into it as of this writing);
153+
# but not 100%, and indeed for business reasons we found it is worthwhile to *test* with C++20 as well, so
154+
# as to ensure code will (at least) build (and thus not mess-over C++20 users; it is now 2025 as of this writing).
155+
# However, loosely speaking, we need only hit a few basic setups likeliest to trigger problems (usually warnings)
156+
# which we'd then fix in the code. Long story short then: in addition to the aforementioned matrix (w/ 17),
157+
# minus exclusions, we would *add* a few specific configs with std=20.
158+
#
159+
# Delightfully that's just how GitHub Actions matrix specs work: The matrix, then remove `exclude`s, then
160+
# add `include`s. ...Unfortunately, nope, the `include`s cannot simply refer to earlier-defined things in
161+
# the matrix, so for example all the properties of a given compiler (e.g. gcc-13: name, version, c-path, cpp-path,
162+
# install) would need to be copy/pasted (stylistically awful/error-prone). (TODO: Perhaps there's some way
163+
# to predefine each compiler's "stuff" in some earlier variable type thing and then just refer to it -- maybe
164+
# something with outputs and fromJSON()... I (ygoldfel) was unable to find clean solid docs/example(s) thereof,
165+
# but a real Actions+YAML badass could probably figure it out. Maybe. OH! And YAML anchors/aliases would be
166+
# *perfect* for this... but Actions guys have somehow not implemented them; it is a long-standing
167+
# request [https://github.com/actions/runner/issues/1182#issuecomment-2317953582].) Hence, for now, we are
168+
# forced to express these post-exclude-includes as simply more excludes. E.g., instead of saying "for C++20
169+
# do just gcc-13" say "do the full matrix but exclude C++20 + gcc-9,10,11."
170+
#
171+
# So now do all that.
142172
matrix:
173+
# First axis in basic 3D matrix is the 2 possible C++ standards.
174+
# For simplicity the Conan-profile-configuring script step simply keys off the `id` instead of our defining
175+
# other properties as for the other 2 more complex axes below (compiler, build config).
176+
cxx-std:
177+
- id: cxx17
178+
- id: cxx20
179+
# Second axis = the many compilers (gcc multiple versions, clang multiple versions).
143180
compiler:
144181
- id: gcc-9
145182
name: gcc
@@ -184,6 +221,7 @@ jobs:
184221
c-path: /usr/bin/clang-17
185222
cpp-path: /usr/bin/clang++-17
186223
install: True
224+
# Last axis are the many build/test configs.
187225
build-test-cfg:
188226
- id: debug
189227
conan-profile-build-type: Debug
@@ -245,43 +283,111 @@ jobs:
245283
sanitizer-name: tsan
246284
no-lto: True
247285

248-
# Again, these exclusions are explained in FLow-IPC workflow counterpart.
286+
# Remove certain values from the 3D matrix so far.
287+
# The overall strategy is outlined in a large-ish comment above.
249288
exclude:
250-
- compiler: { id: gcc-9 }
289+
# Firstly the exclusions from the C++17 sub-matrix (the other being the C++20 sub-matrix).
290+
# Again, these exclusions are explained in Flow-IPC workflow counterpart.
291+
- cxx-std: { id: cxx17 }
292+
compiler: { id: gcc-9 }
251293
build-test-cfg: { id: relwithdebinfo-asan }
252-
- compiler: { id: gcc-10 }
294+
- cxx-std: { id: cxx17 }
295+
compiler: { id: gcc-10 }
253296
build-test-cfg: { id: relwithdebinfo-asan }
254-
- compiler: { id: gcc-11 }
297+
- cxx-std: { id: cxx17 }
298+
compiler: { id: gcc-11 }
255299
build-test-cfg: { id: relwithdebinfo-asan }
256-
- compiler: { id: gcc-13 }
300+
- cxx-std: { id: cxx17 }
301+
compiler: { id: gcc-13 }
257302
build-test-cfg: { id: relwithdebinfo-asan }
258-
- compiler: { id: clang-13 }
303+
- cxx-std: { id: cxx17 }
304+
compiler: { id: clang-13 }
259305
build-test-cfg: { id: relwithdebinfo-asan }
260-
- compiler: { id: gcc-9 }
306+
- cxx-std: { id: cxx17 }
307+
compiler: { id: gcc-9 }
261308
build-test-cfg: { id: relwithdebinfo-ubsan }
262-
- compiler: { id: gcc-10 }
309+
- cxx-std: { id: cxx17 }
310+
compiler: { id: gcc-10 }
263311
build-test-cfg: { id: relwithdebinfo-ubsan }
264-
- compiler: { id: gcc-11 }
312+
- cxx-std: { id: cxx17 }
313+
compiler: { id: gcc-11 }
265314
build-test-cfg: { id: relwithdebinfo-ubsan }
266-
- compiler: { id: gcc-13 }
315+
- cxx-std: { id: cxx17 }
316+
compiler: { id: gcc-13 }
267317
build-test-cfg: { id: relwithdebinfo-ubsan }
268-
- compiler: { id: clang-13 }
318+
- cxx-std: { id: cxx17 }
319+
compiler: { id: clang-13 }
269320
build-test-cfg: { id: relwithdebinfo-ubsan }
270-
- compiler: { id: gcc-9 }
321+
- cxx-std: { id: cxx17 }
322+
compiler: { id: gcc-9 }
323+
build-test-cfg: { id: relwithdebinfo-tsan }
324+
- cxx-std: { id: cxx17 }
325+
compiler: { id: gcc-10 }
271326
build-test-cfg: { id: relwithdebinfo-tsan }
272-
- compiler: { id: gcc-10 }
327+
- cxx-std: { id: cxx17 }
328+
compiler: { id: gcc-11 }
273329
build-test-cfg: { id: relwithdebinfo-tsan }
274-
- compiler: { id: gcc-11 }
330+
- cxx-std: { id: cxx17 }
331+
compiler: { id: gcc-13 }
275332
build-test-cfg: { id: relwithdebinfo-tsan }
276-
- compiler: { id: gcc-13 }
333+
- cxx-std: { id: cxx17 }
334+
compiler: { id: clang-13 }
277335
build-test-cfg: { id: relwithdebinfo-tsan }
278-
- compiler: { id: clang-13 }
336+
# Now the exclusions from the C++20 sub-matrix.
337+
# The basic strategy is noted above; but specifically release-oriented builds tend to go down different
338+
# paths/produce different warnings vs debug-oriented builds -- so we want to try both; and as for which
339+
# compilers to use, on one hand we'll definitely want at least 1 of gcc and clang each, but beyond that
340+
# just the highest version of each is sufficient. TODO: Revisit, especially if users report C++20 problems
341+
# in other environments, and therefore we will have had to fix them; hence test coverage should increase.
342+
#
343+
# So, we essentially want something like:
344+
# include:
345+
# # gcc-<highest> x (2 build-configs).
346+
# - cxx-std: { id: cxx20 }
347+
# compiler: { id: gcc-13 }
348+
# build-test-cfg: { id: debug }
349+
# - cxx-std: { id: cxx20 }
350+
# compiler: { id: gcc-13 }
351+
# build-test-cfg: { id: relwithdebinfo }
352+
# # clang-<highest> x (2 build-configs).
353+
# - cxx-std: { id: cxx20 }
354+
# compiler: { id: clang-17 }
355+
# build-test-cfg: { id: debug }
356+
# - cxx-std: { id: cxx20 }
357+
# compiler: { id: clang-17 }
358+
# build-test-cfg: { id: relwithdebinfo }
359+
# Sadly, as mentioned in the large-ish comment nearer the top, `include` does not let us refer to
360+
# earlier-defined things simply by their `id`s. So we choose to express the inclusions as exclusions
361+
# instead:
362+
# - First, simply exclude all gccs but gcc-13 and all clangs but clang-17.
363+
- cxx-std: { id: cxx20 }
364+
compiler: { id: gcc-9 }
365+
- cxx-std: { id: cxx20 }
366+
compiler: { id: gcc-10 }
367+
- cxx-std: { id: cxx20 }
368+
compiler: { id: gcc-11 }
369+
- cxx-std: { id: cxx20 }
370+
compiler: { id: clang-13 }
371+
- cxx-std: { id: cxx20 }
372+
compiler: { id: clang-15 }
373+
- cxx-std: { id: cxx20 }
374+
compiler: { id: clang-16 }
375+
# - Second, among the remaining C++20 configs eliminate all but debug and relwithdebinfo ones.
376+
- cxx-std: { id: cxx20 }
377+
build-test-cfg: { id: release }
378+
- cxx-std: { id: cxx20 }
379+
build-test-cfg: { id: minsizerel }
380+
- cxx-std: { id: cxx20 }
381+
build-test-cfg: { id: relwithdebinfo-asan }
382+
- cxx-std: { id: cxx20 }
383+
build-test-cfg: { id: relwithdebinfo-ubsan }
384+
- cxx-std: { id: cxx20 }
279385
build-test-cfg: { id: relwithdebinfo-tsan }
280386

281387
# Not using ubuntu-latest, so as to avoid surprises with OS upgrades and such.
282388
runs-on: ubuntu-22.04
283389

284-
name: ${{ matrix.compiler.id }}-${{ matrix.build-test-cfg.id }}
390+
name: ${{ matrix.compiler.id }}-${{ matrix.build-test-cfg.id }}-${{ matrix.cxx-std.id }}
285391

286392
env:
287393
build-dir: ${{ github.workspace }}/build/${{ matrix.build-test-cfg.conan-profile-build-type }}
@@ -383,7 +489,7 @@ jobs:
383489
[settings]
384490
compiler = ${{ matrix.compiler.name }}
385491
compiler.version = ${{ matrix.compiler.version }}
386-
compiler.cppstd = 17
492+
compiler.cppstd = ${{ ((matrix.cxx-std.id == 'cxx20') && '20') || '17' }}
387493
# TODO: Consider testing with LLVM-libc++ also (with clang anyway).
388494
compiler.libcxx = libstdc++11
389495
arch = x86_64
@@ -403,6 +509,10 @@ jobs:
403509
[options]
404510
flow:build = True
405511
flow:doc = False
512+
# `0` here as of this writing would effectively mean `17` too. Specifying `17` explicitly has the same
513+
# effect in the end but goes down a slightly different code-path in FlowLikeCodeGenerate.cmake. It's not
514+
# a huge deal... but it's a little nice to cover more paths, so let's do the `0` path when applicable.
515+
flow:build_cxx_std = ${{ ((matrix.cxx-std.id == 'cxx20') && '20') || '0' }}
406516
EOF
407517
408518
- name: Install Flow dependencies with Conan using the profile
@@ -517,7 +627,7 @@ jobs:
517627
always()
518628
uses: actions/upload-artifact@v4
519629
with:
520-
name: flow-test-logs-${{ matrix.compiler.id }}-${{ matrix.build-test-cfg.id }}
630+
name: flow-test-logs-${{ matrix.compiler.id }}-${{ matrix.build-test-cfg.id }}-${{ matrix.cxx-std.id }}
521631
path: ${{ env.install-dir }}/bin/logs.tgz
522632

523633
doc-and-release:

conanfile.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,18 @@ class FlowRecipe(ConanFile):
3535
options = {
3636
"build": [True, False],
3737
"build_no_lto": [True, False],
38+
39+
# 0 => default (let build script decide, as of this writing 17 meaning C++17) or a #, probably `20` as of
40+
# this writing.
41+
"build_cxx_std": ["ANY"],
42+
3843
"doc": [True, False]
3944
}
4045

4146
default_options = {
4247
"build": True,
4348
"build_no_lto": False,
49+
"build_cxx_std": 0,
4450
"doc": False
4551
}
4652

@@ -83,6 +89,8 @@ def generate(self):
8389
if self.options.build:
8490
if self.options.build_no_lto:
8591
toolchain.variables["CFG_NO_LTO"] = "ON"
92+
if self.options.build_cxx_std != 0:
93+
toolchain.variables["CMAKE_CXX_STANDARD"] = self.options.build_cxx_std
8694
else:
8795
toolchain.variables["CFG_SKIP_CODE_GEN"] = "ON"
8896
if self.options.doc:

src/flow/common.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,12 @@
7070
*
7171
* Note this isn't academic; as of this writing there's at least a C++14-requiring constexpr feature in use in one
7272
* of the headers. So at least C++14 has been required for ages in actual practice. Later, notched it up to C++17
73-
* by similar logic. */
73+
* by similar logic.
74+
*
75+
* Update: We continue to target C++17 (by default) in our build -- but now also support (and, outside
76+
* the source code proper, test) C++20 mode build, albeit without using any C++20-only language or STL features. It is
77+
* conceivable that at some point in the future we'll target C++20 by default (and drop support for C++17 and older).
78+
* Naturally at that point we'd begin using C++20 language/STL features when convenient. */
7479
#if (!defined(__cplusplus)) || (__cplusplus < 201703L)
7580
// Would use static_assert(false), but... it's C++11 and later only. So.
7681
# error "To compile a translation unit that `#include`s any flow/ API headers, use C++17 compile mode or later."

src/flow/net_flow/detail/low_lvl_packet.hpp

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "flow/util/blob.hpp"
2828
#include <boost/endian.hpp>
2929
#include <limits>
30+
#include <type_traits>
3031

3132
namespace flow::net_flow
3233
{
@@ -1220,19 +1221,15 @@ struct Ack_packet::Individual_ack
12201221
*/
12211222
const unsigned int m_rexmit_id;
12221223

1223-
// Constructors/destructor.
1224-
1225-
/// Force direct member initialization even if no member is `const`.
1226-
Individual_ack() = delete;
1227-
1228-
/// Forbid copy construction.
1229-
Individual_ack(const Individual_ack&) = delete;
1224+
/// Make us noncopyable without breaking aggregateness (direct-init).
1225+
[[no_unique_address]] util::Noncopyable m_nc{};
1226+
}; // struct Ack_packet::Individual_ack
12301227

1231-
// Methods.
1232-
1233-
/// Forbid copy assignment.
1234-
void operator=(const Individual_ack&) = delete;
1235-
};
1228+
static_assert(std::is_aggregate_v<Ack_packet::Individual_ack>,
1229+
"We want it to be direct-initializable.");
1230+
static_assert((!std::is_copy_constructible_v<Ack_packet::Individual_ack>)
1231+
&& (!std::is_copy_assignable_v<Ack_packet::Individual_ack>),
1232+
"We want it to be noncopyable but rather passed-around via its ::Ptr.");
12361233

12371234
#pragma pack(push, 1)
12381235

src/flow/net_flow/peer_socket.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,12 @@ void Node::async_acknowledge_packet(Peer_socket::Ptr sock, const Sequence_number
15911591

15921592
const size_t acks_pending_before_this = sock->m_rcv_pending_acks.size();
15931593

1594+
static_assert(std::is_aggregate_v<Peer_socket::Individual_ack>,
1595+
"We want it to be direct-initializable.");
1596+
static_assert((!std::is_copy_constructible_v<Peer_socket::Individual_ack>)
1597+
&& (!std::is_copy_assignable_v<Peer_socket::Individual_ack>),
1598+
"We want it to be noncopyable but rather passed-around via its ::Ptr.");
1599+
15941600
/* Just the starting sequence number sufficient to identify a single packet. The time point saved
15951601
* here is subtracted from time_now() at ACK send time, to compute the artificial delay introduced
15961602
* by ACK delaying (explained just below). This helps other side calculate a more accurate RTT by

src/flow/net_flow/peer_socket.hpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <boost/shared_ptr.hpp>
3737
#include <boost/enable_shared_from_this.hpp>
3838
#include <boost/move/unique_ptr.hpp>
39+
#include <type_traits>
3940

4041
namespace flow::net_flow
4142
{
@@ -2408,16 +2409,10 @@ struct Peer_socket::Individual_ack
24082409
/// Number of bytes in the packet's user data.
24092410
const size_t m_data_size;
24102411

2411-
// Constructors/destructor.
2412-
2413-
/// Force direct member initialization even if no member is `const`.
2414-
Individual_ack(const Individual_ack&) = delete;
2415-
2416-
// Methods.
2417-
2418-
/// Forbid copy assignment.
2419-
Individual_ack& operator=(const Individual_ack&) = delete;
2412+
/// Make us noncopyable without breaking aggregateness (direct-init).
2413+
[[no_unique_address]] util::Noncopyable m_nc{};
24202414
}; // struct Peer_socket::Individual_ack
2415+
// Note: Some static_assert()s about it currently in peer_socket.cpp in a function {} (for boring reasons).
24212416

24222417
// Free functions: in *_fwd.hpp.
24232418

src/flow/util/util.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,31 @@ class Null_interface
6060
virtual ~Null_interface() = 0;
6161
};
6262

63+
/**
64+
* Useful as a no-unique-address private member to make a type noncopyable while keeping that type an aggregate
65+
* (can be direct-initialized).
66+
*
67+
* So you can do: `[[no_unique_address]] flow::util::Noncopyable m_nc{};`.
68+
*
69+
* ### Rationale ###
70+
* The usual technique of deriving from `boost::noncopyable` disables aggregateness. In C++20 declaring
71+
* a `= delete` copy ctor also disables it. This trick still works though.
72+
*/
73+
struct Noncopyable
74+
{
75+
// Constructors/destructor.
76+
77+
/// Makes it possible to instantiate.
78+
Noncopyable() = default;
79+
/// Forbid copying.
80+
Noncopyable(const Noncopyable&) = delete;
81+
82+
// Methods.
83+
84+
/// Forbid copying.
85+
void operator=(const Noncopyable&) = delete;
86+
};
87+
6388
/**
6489
* A simple RAII-pattern class template that, at construction, sets the specified location in memory to a specified
6590
* value, memorizing the previous contents; and at destruction restores the value. E.g.:

tools/cmake/FlowLikeCodeGenerate.cmake

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,13 +267,23 @@ message(VERBOSE "Environment checks passed. Configuring build environment.")
267267

268268
# Set CMake global stuff.
269269

270-
# Compile our own source files in C++17 mode; and refuse to proceed, if the compiler does not support it.
271-
# Note that any `#include`r will also need to follow this, as our headers are not shy about using C++17 features;
272-
# but that's enforced by a check in common.hpp (at least); not by us here somehow.
273-
set(CXX_STD 17)
274-
set(CMAKE_CXX_STANDARD ${CXX_STD})
270+
# CMAKE_CXX_STANDARD controls the C++ standard version with which items are compiled.
271+
# We require C++17 at the lowest for compilation (and for any `#include`r -- but that is enforced via compile-time
272+
# check in universally-included common.hpp, not by us here somehow).
273+
# So if CMAKE_CXX_STANDARD is not specified by CMake invoker, we in fact build in C++17 mode.
274+
# If it *is* specified, then we don't override it. In practice, as of this writing, that means it should be 17
275+
# or 20. (If a lower one is attempted, we don't fight it here -- but common.hpp check will defeat it anyway.)
276+
277+
# Won't decay to lower standard if compiler does not support CMAKE_CXX_STANDARD (will fail instead).
275278
set(CMAKE_CXX_STANDARD_REQUIRED ON)
276-
message(STATUS "C++${CXX_STD} language and STL required.")
279+
280+
if(NOT DEFINED CMAKE_CXX_STANDARD)
281+
set(CMAKE_CXX_STANDARD 17)
282+
message(STATUS "C++${CMAKE_CXX_STANDARD} language and STL required: set by this build script.")
283+
else()
284+
message(STATUS "C++${CMAKE_CXX_STANDARD} language and STL requirement "
285+
"inherited from externally set CMAKE_CXX_STANDARD.")
286+
endif()
277287

278288
# When we do find_package(Threads) to link threading library this will cause it to
279289
# prefer -pthread flag where applicable (as of this writing, just Linux, but perhaps any *nix ultimately).

0 commit comments

Comments
 (0)