├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── any ├── any.h └── tag_invoke.h ├── can ├── can_codec.h ├── can_kernel.h └── frame_packet.h ├── dbc ├── README.md ├── dbc_parser.cpp ├── dbc_parser.h └── parser_template.h ├── example ├── example.cpp └── example.dbc └── v2c ├── README.md ├── v2c_transcoder.cpp └── v2c_transcoder.h /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # IDEs 35 | .vs 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2001-2023 Mireo, EU 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | C++ CAN utilities, including fully compliant CAN DBC C++ parser =============================================================== [![License](https://img.shields.io/badge/license-BSD3-blue.svg)](LICENSE) [![Contributors](https://img.shields.io/github/contributors/mireo/can-utils.svg)](https://github.com/mireo/can-utils/graphs/contributors) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](README.md) [![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)](README.md) [![Issues](https://img.shields.io/github/issues/mireo/can-utils.svg)](https://github.com/mireo/can-utils/issues) Introduction ------------ This repository contains several CAN (Controller Area Network) C++ utilities which could simplify collecting, decoding, transcoding and transferring CAN messages to cloud. Most of the code in the repository is designed to run on an edge device (for example, an embedded telemetry device). However, utilities like CAN DBC parser or CAN frame packet buffer can also be used on server side, thus providing some of the essential tools in [IOT telemetry](https://iotatlas.net/en/patterns/telemetry/) ecosystems. Features -------- * [DBC parser](dbc/README.md) * A complete, customizable and efficient DBC parser written in C++ with full DBC syntax support for all keywords. * [Vehicle-To-Cloud Transcoder](v2c/README.md) * Edge-computing telemetric component that groups, filters, and aggregates CAN signals. Can drastically reduce the amount of data sent from the device over the network. Uses the DBC parser to read and define the CAN network. How to Build ------------ #### 1. Fetch Boost * Download [Boost](https://www.boost.org/users/download/) and move it to your include path The project requires only headers from Boost, so no libraries need to be built. #### 2. Build You can compile the example as follows: ```sh $ g++ -std=c++20 example/example.cpp dbc/dbc_parser.cpp v2c/v2c_transcoder.cpp -I . -o can_example ``` `can-utils` has been tested with Clang, GCC and MSVC on Windows and Linux. It requires C++20. Usage ----- ### Example - [Full source here](example/example.cpp) Build, then run without any command line arguments: ```sh $ ./can_example ``` The example program parses [example.dbc](example/example.dbc), generates millions of random frames, aggregates them with `v2c_transcoder`, and prints the decoded raw signals to the console. Example output: ```py New frame_packet (from 2121812 frames): can_frame at t: 1683709842.116000s, can_id: 4 SOCavg: 574 can_frame at t: 1683709842.116000s, can_id: 6 RawBattCurrent: 10914 SmoothBattCurrent: 10921 BattVoltage: 32760 can_frame at t: 1683709842.516000s, can_id: 2 GPSAccuracy: 118 GPSLongitude: -106019721 GPSLatitude: 26758102 can_frame at t: 1683709842.516000s, can_id: 3 GPSAltitude: -8084 can_frame at t: 1683709842.516000s, can_id: 5 GPSSpeed: 2160 can_frame at t: 1683709842.516000s, can_id: 7 PowerState: 2 can_frame at t: 1683709842.616000s, can_id: 4 SOCavg: 163 can_frame at t: 1683709842.616000s, can_id: 6 RawBattCurrent: -27877 SmoothBattCurrent: -27827 BattVoltage: 32731 ... ``` The signal values are raw decoded bytes, not scaled by the signal's factor or offset. ___ ### DBC Parser - [Full documentation here.](dbc/README.md) The parser can be used as follows: ```cpp custom_dbc dbc_impl; // custom class that implements your logic and data structures bool success = can::parse_dbc(dbc_content, std::ref(dbc_impl)); // parses the DBC // dbc_impl is now populated by the parser and can be used ``` The behavior of the parser is customized by user-defined callbacks invoked when parsing a DBC keyword. Defining the following callback would print all `BO_` objects (messages) in the DBC, and call `add_message()` on `dbc_impl`: ``` cpp inline void tag_invoke( def_bo_cpo, dbc_impl& this_, uint32_t msg_id, std::string msg_name, size_t msg_size, size_t transmitter_ord ) { std::cout << "New message '" << msg_name << "' with ID = " << msg_id << std::endl; this_.add_message(msg_id, msg_name, msg_size); } ``` The full list of callback function signatures, with examples, can be found [here](dbc/README.md). ___ ### V2C Transcoder - [Full documentation here](v2c/README.md) V2C is modeled as a node in the CAN network. It reads CAN frames as input, aggregates their values, and encodes them back into CAN `frame_packets`. To use it, initialize `v2c_transcoder` and then call its `transcode(t, frame)` method with frames read from the CAN socket. `transcode()` periodically returns a `frame_packet` containing the aggregated `can_frames`, ready to be sent over the network. ```cpp can::v2c_transcoder transcoder; can::parse_dbc(read_file("example/example.dbc"), std::ref(transcoder)); while (true) { // read a frame from the CAN socket can_frame frame = read_frame(); auto t = std::chrono::system_clock::now(); auto fp = transcoder.transcode(t, frame); if (fp) { // send the frame_packet over the network send_frame_packet(fp); } } ``` The transcoder's message groups, aggregation types and sampling/sending windows are customized through the DBC directly: ```py EV_ V2CTxTime: 0 [0|60000] "ms" 2000 1 DUMMY_NODE_VECTOR1 V2C; EV_ GPSGroupTxFreq: 0 [0|60000] "ms" 600 11 DUMMY_NODE_VECTOR1 V2C; EV_ EnergyGroupTxFreq: 0 [0|60000] "ms" 500 13 DUMMY_NODE_VECTOR1 V2C; BA_ "AggType" SG_ 7 PowerState "LAST"; BA_ "AggType" SG_ 4 SOCavg "LAST"; BA_ "AggType" SG_ 6 RawBattCurrent "AVG"; BA_ "AggType" SG_ 6 SmoothBattCurrent "AVG"; ``` A more in-depth explanation can be found [here](v2c/README.md). Contributing ------------ When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. You may merge a Pull Request once you have the sign-off from other developers, or you may request the reviewer to merge it for you. License ------- Copyright (c) 2001-2023 Mireo, EU Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Credits ---------- Maintained and authored by [Mireo](https://www.mireo.com/spacetime).

Mireo

-------------------------------------------------------------------------------- /any/any.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | 5 | Read: 6 | 7 | https://github.com/facebookexperimental/libunifex/blob/main/doc/type_erasure.md 8 | 9 | Synopsis: 10 | 11 | template 12 | class any; 13 | 14 | Type-erasing wrapper is a move-only wrapper that implements a small-object optimisation 15 | that allows storing the wrapped object inline if it is smaller than the inline buffer, 16 | and heap-allocates storage for the object if it does not fit in the inline buffer. 17 | 18 | template 19 | class any_copyable; 20 | 21 | Copyable variant of `mireo::any`. Requires wrapped objects to be copy-constructible and 22 | copy-assignable. 23 | 24 | template 25 | class any_function; 26 | 27 | Similar to std::function that can be further customized with CPOs; like mireo::any. 28 | 29 | mireo::any_function the_main = main; 30 | the_main(argc, argv); 31 | 32 | template 33 | class any_copyable_function; 34 | 35 | Copyable variant of `mireo::any_function`. 36 | 37 | mireo::any_copyable_function main1 = &main; 38 | auto main2 = main1; 39 | 40 | */ 41 | 42 | #include 43 | #include "tag_invoke.h" 44 | 45 | namespace mireo { 46 | 47 | // 48 | // this_ 49 | // 50 | 51 | struct this_ { }; 52 | 53 | template 54 | inline constexpr bool is_this_v = false; 55 | template <> 56 | inline constexpr bool is_this_v = true; 57 | template <> 58 | inline constexpr bool is_this_v = true; 59 | template <> 60 | inline constexpr bool is_this_v = true; 61 | template <> 62 | inline constexpr bool is_this_v = true; 63 | template <> 64 | inline constexpr bool is_this_v = true; 65 | template <> 66 | inline constexpr bool is_this_v = true; 67 | 68 | namespace detail { 69 | 70 | struct _ignore { 71 | template _ignore(T&&) noexcept { } 72 | }; 73 | 74 | template 75 | struct _replace_this; 76 | 77 | template <> 78 | struct _replace_this { 79 | template 80 | using apply = Arg; 81 | 82 | template 83 | static Arg&& get(Arg&& arg, _ignore) noexcept { 84 | return (Arg &&) arg; 85 | } 86 | }; 87 | 88 | template <> 89 | struct _replace_this { 90 | template 91 | using apply = T; 92 | 93 | template 94 | static T&& get(_ignore, T& obj) noexcept { 95 | return (T &&) obj; 96 | } 97 | }; 98 | 99 | template <> 100 | struct _replace_this { 101 | template 102 | using apply = T&; 103 | 104 | template 105 | static T& get(_ignore, T& obj) noexcept { 106 | return obj; 107 | } 108 | }; 109 | 110 | template <> 111 | struct _replace_this { 112 | template 113 | using apply = T&&; 114 | 115 | template 116 | static T&& get(_ignore, T& obj) noexcept { 117 | return (T &&) obj; 118 | } 119 | }; 120 | 121 | template <> 122 | struct _replace_this { 123 | template 124 | using apply = const T&; 125 | 126 | template 127 | static const T& get(_ignore, T& obj) noexcept { 128 | return obj; 129 | } 130 | }; 131 | 132 | template <> 133 | struct _replace_this { 134 | template 135 | using type = const T&&; 136 | 137 | template 138 | static const T&& get(_ignore, T& obj) noexcept { 139 | return (const T&&) obj; 140 | } 141 | }; 142 | 143 | template 144 | using _normalize_t = std::conditional_t, Arg, void>; 145 | 146 | template 147 | using replace_this = _replace_this<_normalize_t>; 148 | 149 | template 150 | using replace_this_t = typename replace_this::template apply; 151 | 152 | template 153 | using replace_this_with_void_ptr_t = std::conditional_t, void*, Arg>; 154 | 155 | template 156 | struct _extract_this { 157 | template 158 | TFirst&& operator()(TFirst&& first, TRest&&...) const noexcept { 159 | return (TFirst&&) first; 160 | } 161 | }; 162 | 163 | template 164 | struct _extract_this { 165 | template 166 | decltype(auto) operator()(_ignore, TRest&&... rest) const noexcept { 167 | static_assert(sizeof...(IsThis) > 0, "Arguments to extract_this"); 168 | return _extract_this{ }((TRest &&) rest...); 169 | } 170 | }; 171 | 172 | template 173 | using extract_this = _extract_this...>; 174 | 175 | // 176 | // overload 177 | // 178 | 179 | template 180 | struct _cpo_t { 181 | struct type; 182 | }; 183 | 184 | // This type will have the associated namespaces 185 | // of CPO (by inheritance) but not those of Sig. 186 | template 187 | struct _cpo_t::type : CPO { 188 | constexpr type() = default; 189 | constexpr type(CPO) noexcept {} 190 | 191 | using base_cpo_t = CPO; 192 | using type_erased_signature_t = Sig; 193 | }; 194 | 195 | template 196 | struct base_cpo { 197 | using type = CPO; 198 | }; 199 | 200 | template 201 | struct base_cpo> { 202 | using type = typename CPO::base_cpo_t; 203 | }; 204 | 205 | template 206 | inline constexpr typename _cpo_t::type _cpo { }; 207 | 208 | template 209 | struct _sig { }; 210 | 211 | template 212 | inline constexpr _sig const sig { }; 213 | 214 | template 215 | using base_cpo_t = typename base_cpo::type; 216 | 217 | // 218 | // vtable 219 | // 220 | 221 | template 222 | Ret _vtable_invoke(CPO cpo, replace_this_with_void_ptr_t... args) noexcept { 223 | void* this_ptr = extract_this { }(args...); 224 | return ((CPO &&)cpo)(replace_this::get((decltype(args)&&)args, *static_cast(this_ptr))...); 225 | } 226 | 227 | template 228 | class vtable_entry; 229 | 230 | template 231 | class vtable_entry { 232 | public: 233 | using fn_t = Ret(base_cpo_t, replace_this_with_void_ptr_t...) noexcept; 234 | 235 | constexpr fn_t* get() const noexcept { 236 | return _fn; 237 | } 238 | 239 | template 240 | static constexpr vtable_entry create() noexcept { 241 | return vtable_entry { &_vtable_invoke, T, Ret, Args...> }; 242 | } 243 | 244 | private: 245 | explicit constexpr vtable_entry(fn_t* fn) noexcept : _fn(fn) { 246 | } 247 | 248 | fn_t* _fn; 249 | }; 250 | 251 | template 252 | class vtable_entry { 253 | public: 254 | using fn_t = Ret(base_cpo_t, replace_this_with_void_ptr_t...); 255 | 256 | constexpr fn_t* get() const noexcept { 257 | return _fn; 258 | } 259 | 260 | template 261 | static constexpr vtable_entry create() noexcept { 262 | return vtable_entry { &_vtable_invoke, T, Ret, Args...> }; 263 | } 264 | 265 | private: 266 | explicit constexpr vtable_entry(fn_t* fn) noexcept : _fn(fn) { 267 | } 268 | 269 | fn_t* _fn; 270 | }; 271 | 272 | template 273 | class vtable : private vtable_entry... { 274 | 275 | explicit constexpr vtable(vtable_entry... entries) noexcept : vtable_entry { entries }... { 276 | } 277 | 278 | public: 279 | template 280 | static const vtable* inst() { 281 | static constexpr vtable v { vtable_entry::template create()... }; 282 | return &v; 283 | }; 284 | 285 | template 286 | constexpr auto get() const noexcept -> typename vtable_entry::fn_t* { 287 | const vtable_entry& entry = *this; 288 | return entry.get(); 289 | } 290 | }; 291 | 292 | // 293 | // get_wrapped_object / with_forwarding_tag_invoke 294 | // 295 | 296 | struct _get_wrapped_object_cpo { 297 | template requires tag_invocable<_get_wrapped_object_cpo, T> 298 | auto operator()(T&& wrapper) const noexcept(is_nothrow_tag_invocable_v<_get_wrapped_object_cpo, T>) -> 299 | tag_invoke_result_t<_get_wrapped_object_cpo, T> 300 | { 301 | return mireo::tag_invoke(*this, static_cast(wrapper)); 302 | } 303 | }; 304 | 305 | inline constexpr _get_wrapped_object_cpo get_wrapped_object { }; 306 | 307 | template 308 | struct _with_forwarding_tag_invoke; 309 | 310 | template 311 | using with_forwarding_tag_invoke = typename _with_forwarding_tag_invoke< 312 | Derived, 313 | base_cpo_t, 314 | typename CPO::type_erased_signature_t 315 | >::type; 316 | 317 | // noexcept(false) specialisation 318 | template 319 | struct _with_forwarding_tag_invoke { 320 | struct type { 321 | friend Ret tag_invoke(CPO cpo, replace_this_t... args) { 322 | auto& wrapper = extract_this{}(args...); 323 | auto& wrapped = get_wrapped_object(wrapper); 324 | return static_cast(cpo)(replace_this::get(static_cast(args), wrapped)...); 325 | } 326 | }; 327 | }; 328 | 329 | // noexcept(true) specialisation 330 | template 331 | struct _with_forwarding_tag_invoke { 332 | struct type { 333 | friend Ret tag_invoke(CPO cpo, replace_this_t... args) noexcept { 334 | auto& wrapper = extract_this{}(args...); 335 | auto& wrapped = get_wrapped_object(wrapper); 336 | return static_cast(cpo)(replace_this::get(static_cast(args), wrapped)...); 337 | } 338 | }; 339 | }; 340 | 341 | // 342 | // with_type_erased_tag_invoke 343 | // 344 | 345 | template 346 | struct _with_type_erased_tag_invoke; 347 | 348 | template 349 | struct _with_type_erased_tag_invoke { 350 | struct type { 351 | friend Ret tag_invoke(base_cpo_t cpo, replace_this_t... args) { 352 | using cpo_t = base_cpo_t; 353 | auto&& t = extract_this{ }((decltype(args)&&)args...); 354 | void* ptr = get_object_address(t); 355 | auto* fn = get_vtable(t)->template get(); 356 | return fn((cpo_t&&) cpo, replace_this::get((decltype(args)&&)args, ptr)...); 357 | } 358 | }; 359 | }; 360 | 361 | // 362 | // any_heap_allocated_storage 363 | // 364 | 365 | template 366 | class _any_heap_allocated_storage { 367 | struct state; 368 | 369 | using allocator_type = typename std::allocator_traits::template rebind_alloc; 370 | using allocator_traits = std::allocator_traits; 371 | 372 | // This is the state that is actually heap-allocated. 373 | struct state { 374 | template requires std::constructible_from 375 | explicit state(std::allocator_arg_t, allocator_type allocator, std::in_place_type_t, Args&&... args) : 376 | object(static_cast(args)...), 377 | allocator(std::move(allocator)) 378 | { 379 | } 380 | 381 | [[no_unique_address]] T object; 382 | [[no_unique_address]] allocator_type allocator; 383 | }; 384 | 385 | // This is the base-class object that holds the pointer to the 386 | // heap-allocated state. 387 | struct base { 388 | template requires std::constructible_from 389 | base(std::allocator_arg_t, allocator_type allocator, std::in_place_type_t, Args&&... args) { 390 | _state = allocator_traits::allocate(allocator, 1); 391 | allocator_traits::construct( 392 | allocator, 393 | _state, 394 | std::allocator_arg, 395 | allocator, 396 | std::in_place_type, 397 | static_cast(args)... 398 | ); 399 | } 400 | 401 | template requires ( 402 | !std::same_as && 403 | std::constructible_from 404 | ) 405 | explicit base(std::allocator_arg_t, Allocator allocator, std::in_place_type_t, Args&&... args) : 406 | base( 407 | std::allocator_arg, 408 | allocator_type(std::move(allocator)), 409 | std::in_place_type, 410 | static_cast(args)... 411 | ) 412 | { 413 | } 414 | 415 | base(const base& other) requires std::copy_constructible : 416 | base( 417 | std::allocator_arg, 418 | const_cast(other._state->allocator), 419 | std::in_place_type, 420 | const_cast(other._state->object) 421 | ) 422 | { 423 | } 424 | 425 | base(base&& other) noexcept : _state(std::exchange(other._state, nullptr)) { 426 | } 427 | 428 | ~base() { 429 | if (_state != nullptr) { 430 | allocator_type alloc = std::move(_state->allocator); 431 | _state->~state(); 432 | std::allocator_traits::deallocate(alloc, _state, 1); 433 | } 434 | } 435 | 436 | private: 437 | friend T& tag_invoke(tag_t, base& self) noexcept { 438 | return self._state->object; 439 | } 440 | 441 | friend const T& tag_invoke(tag_t, const base& self) noexcept { 442 | return self._state->object; 443 | } 444 | 445 | state* _state; 446 | }; 447 | 448 | template 449 | struct concrete final { 450 | class type : public base, private detail::with_forwarding_tag_invoke... { 451 | using base::base; 452 | }; 453 | }; 454 | 455 | public: 456 | template 457 | using type = typename concrete::type; 458 | }; 459 | 460 | template 461 | using any_heap_allocated_storage = typename _any_heap_allocated_storage::template type; 462 | 463 | // 464 | // _destroy_cpo / _move_construct_cpo / _copy_construct_cpo / _invoke_cpo 465 | // 466 | 467 | struct _destroy_cpo { 468 | using type_erased_signature_t = void(this_&) noexcept; 469 | 470 | template 471 | void operator()(T& object) const noexcept { 472 | object.~T(); 473 | } 474 | }; 475 | 476 | struct _move_construct_cpo { 477 | using type_erased_signature_t = void(void* p, this_&& src) noexcept; 478 | 479 | template 480 | void operator()(void* p, T&& src) const noexcept { 481 | ::new (p) T(static_cast(src)); 482 | } 483 | }; 484 | 485 | struct _copy_construct_cpo { 486 | using type_erased_signature_t = void(void* p, const this_& src) noexcept; 487 | 488 | template 489 | void operator()(void* p, const T& src) const noexcept { 490 | ::new (p) T(src); 491 | } 492 | }; 493 | 494 | template struct _invoke_cpo; 495 | 496 | template 497 | struct _invoke_cpo { 498 | using type_erased_signature_t = R(this_&, Args...); 499 | 500 | template 501 | static constexpr bool with_tag_invoke_v = mireo::is_tag_invocable_v<_invoke_cpo, F, Args...>; 502 | 503 | template 504 | static constexpr bool nothrow_invoke_v = with_tag_invoke_v ? 505 | mireo::is_nothrow_tag_invocable_v<_invoke_cpo, F, Args...> : 506 | std::is_nothrow_invocable_v; 507 | 508 | template 509 | R operator()(F&& fn, Args... arg) const noexcept(nothrow_invoke_v) { 510 | if constexpr (with_tag_invoke_v) { 511 | return mireo::tag_invoke(*this, (F&&)fn, (Args&&)arg...); 512 | } 513 | else { 514 | return ((F&&)fn)((Args&&)arg...); 515 | } 516 | } 517 | }; 518 | 519 | } // namespace detail 520 | 521 | // 522 | // with_type_erased_tag_invoke 523 | // 524 | // When defining a type-erasing wrapper type, Derived, you can privately inherit 525 | // from this class to have the type opt-in to customising the specified CPO. 526 | // 527 | 528 | template 529 | using with_type_erased_tag_invoke = typename detail::_with_type_erased_tag_invoke< 530 | Derived, 531 | CPO, 532 | typename 533 | CPO::type_erased_signature_t 534 | >::type; 535 | 536 | // 537 | // _any_object 538 | // 539 | 540 | template 541 | struct _any_object { 542 | // Pad size/alignment out to allow storage of at least a pointer. 543 | static constexpr std::size_t inline_alignment = alignof(void*); 544 | static constexpr std::size_t inline_size = InlineSize < sizeof(void*) ? sizeof(void*) : InlineSize; 545 | 546 | // move-constructor is (must be) noexcept 547 | template 548 | static constexpr bool can_be_stored_inplace_v = (sizeof(T) <= inline_size && alignof(T) <= inline_alignment); 549 | 550 | static constexpr bool copyable_v = IsCopyable; 551 | 552 | class type; 553 | }; 554 | 555 | template 556 | class _any_object::type : 557 | private with_type_erased_tag_invoke... 558 | { 559 | using vtable_t = std::conditional_t< 560 | _any_object::copyable_v, 561 | detail::vtable, 562 | detail::vtable 563 | >; 564 | 565 | const vtable_t* _vtable = nullptr; 566 | alignas(inline_alignment) std::byte _storage[inline_size]; 567 | 568 | public: 569 | type() = default; 570 | 571 | template requires (!std::is_same_v>) 572 | type(T&& object) : type(std::in_place_type>, static_cast(object)) { 573 | } 574 | 575 | template 576 | explicit type(std::allocator_arg_t, Allocator allocator, T&& value) noexcept : 577 | type( 578 | std::allocator_arg, 579 | std::move(allocator), 580 | std::in_place_type>, 581 | static_cast(value) 582 | ) 583 | { 584 | } 585 | 586 | template requires _any_object::can_be_stored_inplace_v 587 | explicit type(std::in_place_type_t, Args&&... args) : _vtable(vtable_t::template inst()) { 588 | ::new (static_cast(&_storage)) T(static_cast(args)...); 589 | } 590 | 591 | template requires (!_any_object::can_be_stored_inplace_v) 592 | explicit type(std::in_place_type_t, Args&&... args) : 593 | type(std::allocator_arg, DefaultAllocator(), std::in_place_type, static_cast(args)...) 594 | { 595 | } 596 | 597 | template requires _any_object::can_be_stored_inplace_v 598 | explicit type(std::allocator_arg_t, Allocator, std::in_place_type_t, Args&&... args) noexcept : 599 | type(std::in_place_type, static_cast(args)...) 600 | { 601 | } 602 | 603 | template requires (!_any_object::can_be_stored_inplace_v) 604 | explicit type(std::allocator_arg_t, Alloc alloc, std::in_place_type_t, Args&&... args) : 605 | _vtable(vtable_t::template inst>()) 606 | { 607 | ::new (static_cast(&_storage)) detail::any_heap_allocated_storage( 608 | std::allocator_arg, 609 | std::move(alloc), 610 | std::in_place_type, 611 | static_cast(args)... 612 | ); 613 | } 614 | 615 | type(const type& other) noexcept requires _any_object::copyable_v : _vtable(other._vtable) { 616 | if (_vtable) { 617 | auto* copy_cons = _vtable->template get(); 618 | copy_cons(detail::_copy_construct_cpo { }, &_storage, get_object_address(other)); 619 | } 620 | } 621 | 622 | type(type&& other) noexcept : _vtable(other._vtable) { 623 | if (_vtable) { 624 | auto* move_cons = _vtable->template get(); 625 | move_cons(detail::_move_construct_cpo{ }, &_storage, &other._storage); 626 | } 627 | } 628 | 629 | ~type() noexcept { 630 | if (_vtable) { 631 | auto* destroy = _vtable->template get(); 632 | destroy(detail::_destroy_cpo{ }, &_storage); 633 | } 634 | } 635 | 636 | type& operator=(const type& other) noexcept requires _any_object::copyable_v { 637 | if (std::addressof(other) == this) 638 | return *this; 639 | 640 | if (_vtable) { 641 | auto* destroy = _vtable->template get(); 642 | destroy(detail::_destroy_cpo{}, &_storage); 643 | } 644 | _vtable = other._vtable; 645 | if (_vtable) { 646 | auto* copy_cons = _vtable->template get(); 647 | copy_cons(detail::_copy_construct_cpo { }, &_storage, get_object_address(other)); 648 | } 649 | return *this; 650 | } 651 | 652 | type& operator=(type&& other) noexcept { 653 | if (std::addressof(other) == this) 654 | return *this; 655 | 656 | if (_vtable) { 657 | auto* destroy = _vtable->template get(); 658 | destroy(detail::_destroy_cpo{ }, &_storage); 659 | } 660 | _vtable = other._vtable; 661 | if (_vtable) { 662 | auto* move_cons = _vtable->template get(); 663 | move_cons(detail::_move_construct_cpo{ }, &_storage, &other._storage); 664 | } 665 | return *this; 666 | } 667 | 668 | template requires 669 | _any_object::can_be_stored_inplace_v> && 670 | (!std::is_same_v>) 671 | type& operator=(T&& value) noexcept { 672 | if (_vtable) { 673 | auto* destroy = _vtable->template get(); 674 | destroy(detail::_destroy_cpo{ }, &_storage); 675 | } 676 | using value_type = std::remove_cvref_t; 677 | ::new (static_cast(&_storage)) value_type(static_cast(value)); 678 | _vtable = vtable_t::template inst(); 679 | return *this; 680 | } 681 | 682 | template requires 683 | (!_any_object::can_be_stored_inplace_v>) && 684 | (!std::is_same_v>) 685 | type& operator=(T&& value) noexcept { 686 | if (_vtable) { 687 | auto* destroy = _vtable->template get(); 688 | destroy(detail::_destroy_cpo{ }, &_storage); 689 | } 690 | using value_type = detail::any_heap_allocated_storage, DefaultAllocator, CPOs...>; 691 | ::new (static_cast(&_storage)) value_type( 692 | std::allocator_arg, 693 | DefaultAllocator { }, 694 | std::in_place_type>, 695 | static_cast(value) 696 | ); 697 | _vtable = vtable_t::template inst(); 698 | return *this; 699 | } 700 | 701 | explicit operator bool() const noexcept { 702 | return _vtable != nullptr; 703 | } 704 | 705 | private: 706 | friend const vtable_t* get_vtable(const type& self) noexcept { 707 | return self._vtable; 708 | } 709 | 710 | friend void* get_object_address(const type& self) noexcept { 711 | return const_cast(static_cast(&self._storage)); 712 | } 713 | }; 714 | 715 | // 716 | // _any_function_t 717 | // 718 | 719 | template 720 | class _any_function_t; 721 | 722 | template 723 | class _any_function_t : 724 | public _any_object, CPOs...>::type 725 | { 726 | using invoke_cpo = detail::_invoke_cpo; 727 | using base_t = typename _any_object< 728 | InlineSize, 729 | DefaultAllocator, 730 | IsCopyable, 731 | detail::_invoke_cpo, 732 | CPOs... 733 | >::type; 734 | 735 | public: 736 | using result_type = R; 737 | 738 | using base_t::base_t; // use constructors from base 739 | 740 | R operator()(Args... arg) const noexcept { 741 | auto* invoke = get_vtable(*this)->template get(); 742 | return invoke(invoke_cpo { }, get_object_address(*this), (Args&&)arg...); 743 | } 744 | }; 745 | 746 | // 747 | // basic_any / basic_any_copyable 748 | // 749 | 750 | template 751 | using basic_any_copyable_t = typename _any_object::type; 752 | 753 | template 754 | using basic_any_t = typename _any_object::type; 755 | 756 | template 757 | using basic_any_copyable = basic_any_copyable_t...>; 758 | 759 | template 760 | using basic_any = basic_any_t...>; 761 | 762 | // 763 | // any / any_copyable 764 | // 765 | 766 | template 767 | using any_copyable_t = basic_any_copyable_t<4 * sizeof(void*), std::allocator, CPOs...>; 768 | 769 | template 770 | using any_copyable = any_copyable_t...>; 771 | 772 | template 773 | using any_t = basic_any_t<4 * sizeof(void*), std::allocator, CPOs...>; 774 | 775 | template 776 | using any = any_t...>; 777 | 778 | // 779 | // basic_any_function / basic_any_copyable_function 780 | // 781 | 782 | template 783 | using basic_any_function_t = _any_function_t; 784 | 785 | template 786 | using basic_any_function = basic_any_function_t...>; 787 | 788 | template 789 | using basic_any_copyable_function_t = _any_function_t; 790 | 791 | template 792 | using basic_any_copyable_function = basic_any_copyable_function_t< 793 | Sig, 794 | InlineSize, 795 | DefaultAllocator, 796 | mireo::tag_t... 797 | >; 798 | 799 | // 800 | // any_function / any_copyable_function 801 | // 802 | 803 | template 804 | using any_function_t = basic_any_function_t, CPOs...>; 805 | 806 | template 807 | using any_function = any_function_t...>; 808 | 809 | template 810 | using any_copyable_function_t = basic_any_copyable_function_t< 811 | Sig, 812 | 4 * sizeof(void*), 813 | std::allocator, 814 | CPOs... 815 | >; 816 | 817 | template 818 | using any_copyable_function = any_copyable_function_t...>; 819 | 820 | // 821 | // any_invoke 822 | // 823 | 824 | template 825 | inline constexpr detail::_invoke_cpo any_invoke { }; 826 | 827 | // 828 | // overload 829 | // 830 | 831 | template 832 | using overload_t = typename detail::_cpo_t, Sig>::type; 833 | 834 | template 835 | constexpr typename detail::_cpo_t::type const& overload(CPO const&, detail::_sig = { }) noexcept { 836 | return detail::_cpo; 837 | } 838 | 839 | } // namespace mireo 840 | -------------------------------------------------------------------------------- /any/tag_invoke.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | 5 | Read: 6 | 7 | https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf 8 | 9 | Example: 10 | 11 | // Define a new CPO named mylib::foo() 12 | // 13 | // “Customization point object.” This is a notion introduced by Eric Niebler’s Ranges library. 14 | // [http://eel.is/c++draft/customization.point.object#1] 15 | 16 | namespace mylib { 17 | 18 | inline constexpr struct foo_cpo { 19 | template 20 | auto operator()(const T& x) const -> mireo::tag_invoke_result_t { 21 | // CPO dispatches to tag_invoke() call; passes the CPO itself as first argument. 22 | return mireo::tag_invoke(*this, x); 23 | } 24 | } foo; 25 | 26 | } 27 | 28 | // Use the mylib::foo() CPO 29 | template requires std::invocable, const T&> 30 | void print_foo(const T& x) { 31 | // Just call the CPO like an ordinary function 32 | std::cout << mylib::foo(x) << std::endl; 33 | } 34 | 35 | // Customise the mylib::foo() CPO for othertype 36 | namespace otherlib { 37 | 38 | struct othertype { 39 | int x; 40 | 41 | friend int tag_invoke(mireo::tag_t, const othertype& x) { 42 | return x.x; 43 | } 44 | }; 45 | 46 | } 47 | 48 | // Can now call print_foo() function. 49 | void example() { 50 | otherlib::othertype x; 51 | print_foo(x); 52 | } 53 | 54 | */ 55 | 56 | #include 57 | 58 | namespace mireo { 59 | 60 | namespace _tag_invoke { 61 | 62 | struct _fn { 63 | template 64 | constexpr auto operator()(CPO cpo, Args&&... args) const 65 | noexcept(noexcept(tag_invoke((CPO &&) cpo, (Args &&) args...))) 66 | -> decltype(tag_invoke((CPO &&) cpo, (Args &&) args...)) 67 | { 68 | return tag_invoke((CPO &&) cpo, (Args &&) args...); 69 | } 70 | }; 71 | 72 | template 73 | using tag_invoke_result_t = decltype(tag_invoke(std::declval(), std::declval()...)); 74 | 75 | using yes_type = char; 76 | using no_type = char(&)[2]; 77 | 78 | template 79 | auto try_tag_invoke(int) noexcept(noexcept(tag_invoke(std::declval(), std::declval()...))) 80 | -> decltype(static_cast(tag_invoke(std::declval(), std::declval()...)), yes_type { }); 81 | 82 | template 83 | no_type try_tag_invoke(...) noexcept(false); 84 | 85 | template