├── LICENSE └── string.hpp /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Mashpoe 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /string.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // string.hpp 3 | // cpp-string 4 | // 5 | // Created by Mashpoe on 7/28/19. 6 | // 7 | 8 | #ifndef string_hpp 9 | #define string_hpp 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace mp { 17 | 18 | class string { 19 | private: 20 | 21 | // compile time constants 22 | enum { 23 | in_situ_capacity = 16, 24 | in_situ_size = in_situ_capacity - 1 25 | }; 26 | 27 | char* str; 28 | size_t len; 29 | union { 30 | size_t capacity; 31 | // last byte has flag bit, 1 = large string 32 | // 0 = small string and null terminator 33 | char data[in_situ_capacity]; 34 | } in_situ; 35 | 36 | inline void init(size_t length) { 37 | len = length; 38 | if (length > in_situ_size) { 39 | size_t capacity = length + 1; 40 | 41 | in_situ.capacity = capacity; 42 | in_situ.data[in_situ_size] = '\1'; 43 | 44 | str = (char*)malloc(capacity); 45 | } else { 46 | in_situ.data[in_situ_size] = '\0'; 47 | str = in_situ.data; 48 | } 49 | } 50 | 51 | // change the string's capacity if the new capacity is greater 52 | // does not change len 53 | void resize(size_t new_capacity) { 54 | 55 | // check capacity using flag bit 56 | if (in_situ.data[in_situ_size] == '\1') { 57 | 58 | if (new_capacity > in_situ.capacity) { 59 | in_situ.capacity = new_capacity; 60 | str = (char*)realloc(str, new_capacity); 61 | } 62 | 63 | } else { 64 | // capacity <= in_situ_size 65 | if (new_capacity > in_situ_size) { 66 | 67 | // move data to the heap 68 | char* new_str = (char*)malloc(new_capacity); 69 | 70 | // copy str including null teminator 71 | memcpy(new_str, str, len + 1); 72 | 73 | str = new_str; 74 | 75 | // change flag bit to large string 76 | in_situ.data[in_situ_size] = '\1'; 77 | 78 | in_situ.capacity = new_capacity; 79 | 80 | } 81 | 82 | } 83 | } 84 | 85 | // private constructor to avoid copying 86 | string(size_t length, void* value) : str((char*)value), len(length) {} 87 | 88 | // creates a string with enough capacity for length 89 | string(size_t length, size_t capacity) { 90 | len = length; 91 | if (capacity > in_situ_capacity) { 92 | str = (char*)malloc(capacity); 93 | in_situ.capacity = capacity; 94 | // set flag bit 95 | in_situ.data[in_situ_size] = '\1'; 96 | } else { 97 | str = in_situ.data; 98 | in_situ.data[in_situ_size] = '\0'; 99 | } 100 | } 101 | 102 | public: 103 | 104 | // initialize str with 1 null byte "" 105 | string() { 106 | str = in_situ.data; 107 | len = 0; 108 | in_situ.data[0] = '\0'; 109 | in_situ.data[in_situ_size] = '\0'; 110 | } 111 | string(decltype(nullptr)) { 112 | str = in_situ.data; 113 | len = 0; 114 | in_situ.data[0] = '\0'; 115 | in_situ.data[in_situ_size] = '\0'; 116 | } 117 | 118 | string(const char* value) { 119 | if (value == nullptr) { 120 | len = 0; 121 | in_situ.data[0] = '\0'; 122 | in_situ.data[15] = '\0'; 123 | return; 124 | } 125 | 126 | init(strlen(value)); 127 | memcpy(str, value, len); 128 | return; 129 | } 130 | string(const char* value, size_t length) { 131 | init(length); 132 | memcpy(str, value, len); 133 | } 134 | 135 | // implement your own constructors for other types 136 | // must be explicit to avoid accidental conversions 137 | template 138 | explicit string(T value) { 139 | std::string str_value = std::to_string(value); 140 | init(str_value.length()); 141 | memcpy(str, str_value.c_str(), len); 142 | } 143 | // an example of a custom constructor for a specific type 144 | // we don't need to allocate because it will fit on the stack 145 | explicit string(bool value) { 146 | if (value) { 147 | init(4); 148 | memcpy(str, "true", len); 149 | } else { 150 | init(5); 151 | memcpy(str, "false", len); 152 | } 153 | } 154 | 155 | 156 | ~string() { 157 | // this can be manually set to 1 for move semantics 158 | if (in_situ.data[in_situ_size] == '\1') { 159 | free(str); 160 | } 161 | } 162 | 163 | inline size_t size() { 164 | return len; 165 | } 166 | 167 | inline size_t length() { 168 | return len; 169 | } 170 | 171 | // copy constructor 172 | string(string const& other) { 173 | 174 | // check flag bit 175 | if (other.in_situ.data[in_situ_size] == '\1') { 176 | len = other.len; 177 | in_situ.capacity = len + 1; 178 | in_situ.data[in_situ_size] = '\1'; 179 | 180 | str = (char*)malloc(in_situ.capacity); 181 | memcpy(str, other.str, in_situ.capacity); 182 | } else { 183 | // just copy the stack data 184 | str = in_situ.data; 185 | len = other.len; 186 | memcpy(&in_situ.data, &other.in_situ.data, len + 1); 187 | in_situ.data[in_situ_size] = '\0'; 188 | } 189 | 190 | } 191 | 192 | // copy assignment 193 | string& operator = (string const& other) { 194 | 195 | // check flag bit 196 | if (other.in_situ.data[in_situ_size] == '\1') { 197 | len = other.len; 198 | in_situ.capacity = len + 1; 199 | in_situ.data[in_situ_size] = '\1'; 200 | 201 | str = (char*)malloc(in_situ.capacity); 202 | memcpy(str, other.str, in_situ.capacity); 203 | } else { 204 | // just copy the stack data 205 | str = in_situ.data; 206 | len = other.len; 207 | memcpy(&in_situ.data, &other.in_situ.data, len + 1); 208 | in_situ.data[in_situ_size] = '\0'; 209 | } 210 | 211 | return *this; 212 | 213 | } 214 | 215 | // move constructor 216 | string(string&& other) noexcept { 217 | 218 | // check flag bit 219 | if (other.in_situ.data[in_situ_size] == '\1') { 220 | len = other.len; 221 | in_situ.capacity = other.in_situ.capacity; 222 | in_situ.data[in_situ_size] = '\1'; 223 | 224 | // take ownership 225 | str = other.str; 226 | // set other flag bit to 0 so destructor won't free 227 | other.in_situ.data[in_situ_size] = '\0'; 228 | } else { 229 | // just copy the stack data 230 | str = in_situ.data; 231 | len = other.len; 232 | memcpy(&in_situ.data, &other.in_situ.data, len + 1); 233 | in_situ.data[in_situ_size] = '\0'; 234 | } 235 | 236 | } 237 | 238 | // move assignment 239 | string& operator = (string&& other) noexcept { 240 | // unfortunate but necessary repitition of code 241 | if (this != &other) { 242 | // check flag bit 243 | if (other.in_situ.data[in_situ_size] == '\1') { 244 | len = other.len; 245 | in_situ.capacity = other.in_situ.capacity; 246 | in_situ.data[in_situ_size] = '\1'; 247 | 248 | // take ownership 249 | str = other.str; 250 | // set other flag bit to 0 so destructor won't free 251 | other.in_situ.data[in_situ_size] = '\0'; 252 | } else { 253 | // just copy the stack data 254 | str = in_situ.data; 255 | len = other.len; 256 | memcpy(&in_situ.data, &other.in_situ.data, len + 1); 257 | in_situ.data[in_situ_size] = '\0'; 258 | } 259 | } 260 | return *this; 261 | } 262 | 263 | const char* c_str() const { 264 | return str; 265 | } 266 | 267 | bool empty() const { 268 | return len == 0; 269 | } 270 | 271 | operator const char* () { 272 | return str; 273 | } 274 | 275 | char& operator [] (int pos) { 276 | return str[pos]; 277 | } 278 | 279 | bool operator ! () { 280 | return len == 0; 281 | } 282 | 283 | explicit operator bool() { 284 | return len != 0; 285 | } 286 | 287 | bool operator == (const char* value) { 288 | return strcmp(str, value) == 0; 289 | } 290 | 291 | bool operator != (const char* value) { 292 | return strcmp(str, value) != 0; 293 | } 294 | 295 | bool operator == (string value) { 296 | return len == value.len && strcmp(str, value.str) == 0; 297 | } 298 | 299 | void insert(size_t pos, const string& value) { 300 | 301 | // allocate sum of both string lengths + 1 for null terminator 302 | size_t new_len = len + value.len; 303 | resize(new_len + 1); 304 | 305 | // move the right side of pos 306 | memmove(&str[pos + value.len], &str[pos], len - pos); 307 | memcpy(&str[pos], value.str, value.len); 308 | 309 | // add null terminator 310 | str[new_len] = '\0'; 311 | 312 | len = new_len; 313 | } 314 | 315 | void insert(size_t pos, const char* value) { 316 | 317 | // allocate sum of both string lengths + 1 for null terminator 318 | size_t value_len = strlen(value); 319 | size_t new_len = len + value_len; 320 | resize(new_len + 1); 321 | 322 | // move the right side of pos 323 | memmove(&str[pos + value_len], &str[pos], len - pos); 324 | memcpy(&str[pos], value, value_len); 325 | 326 | // add null terminator 327 | str[new_len] = '\0'; 328 | 329 | len = new_len; 330 | } 331 | 332 | template 333 | void insert(size_t pos, T const& value) { 334 | insert(pos, string(value)); 335 | } 336 | 337 | void append(string const& value) { 338 | 339 | // allocate sum of both string lengths + 1 for null terminator 340 | size_t new_len = len + value.len; 341 | resize(new_len + 1); 342 | 343 | // copy the other string 344 | memcpy(&str[len], value.str, value.len); 345 | 346 | // add null terminator 347 | str[new_len] = '\0'; 348 | 349 | // update length 350 | len = new_len; 351 | } 352 | 353 | void append(const char* value) { 354 | 355 | size_t value_len = strlen(value); 356 | 357 | // allocate sum of both string lengths + 1 for null terminator 358 | size_t new_len = len + value_len; 359 | resize(new_len + 1); 360 | 361 | // copy the other string 362 | memcpy(&str[len], value, value_len); 363 | 364 | // add null terminator 365 | str[new_len] = '\0'; 366 | 367 | // update length 368 | len = new_len; 369 | } 370 | 371 | template 372 | void append(T const& value) { 373 | append(string(value)); 374 | } 375 | 376 | inline string& operator += (string const& value) { 377 | append(value); 378 | return *this; 379 | } 380 | 381 | inline string& operator += (const char* value) { 382 | append(value); 383 | return *this; 384 | } 385 | 386 | template 387 | string& operator += (T const& value) { 388 | append(string(value)); 389 | return *this; 390 | } 391 | 392 | void erase(size_t pos, size_t count) { 393 | size_t new_len = len - count; 394 | 395 | // move the right side of the erased portion 396 | memmove(&str[pos], &str[pos + count], len - pos - count); 397 | 398 | // add null terminator 399 | str[new_len] = '\0'; 400 | len = new_len; 401 | } 402 | 403 | void remove(size_t pos) { 404 | erase(pos, 1); 405 | } 406 | 407 | // concatenation overloads 408 | string friend operator + (string const& left, string const& right); 409 | // rvalue optimization (we can only optimize the left side) 410 | string friend operator + (string&& left, string const& right); 411 | 412 | // generic types 413 | template 414 | string friend operator + (string const& left, T const& right); 415 | template 416 | string friend operator + (T const& left, const string& right); 417 | // rvalue optimization (we can only optimize the left side) 418 | template 419 | string friend operator + (string&& left, T const& right); 420 | 421 | // regular c string 422 | string friend operator + (string const& left, const char* right); 423 | string friend operator + (const char* left, string const& right); 424 | // rvalue optimization (we can only optimize the left side) 425 | string friend operator + (string&& left, const char* right); 426 | }; 427 | 428 | string operator + (string const& left, string const& right) { 429 | // allocate sum of both string lengths + 1 for null terminator 430 | size_t new_len = left.len + right.len; 431 | // call private constructor to allocate an empty string with the given length 432 | string s(new_len, new_len + 1); 433 | 434 | // copy the contents of both strings 435 | memcpy(s.str, left.str, left.len); 436 | memcpy(&s.str[left.len], right.str, right.len); 437 | 438 | // add null terminator 439 | s.str[new_len] = '\0'; 440 | 441 | return s; 442 | } 443 | 444 | string operator + (string&& left, string const& right) { 445 | /* left is an rvalue, so we can just modify then move it 446 | to increase performance */ 447 | 448 | // allocate sum of both string lengths + 1 for null terminator 449 | size_t new_len = left.len + right.len; 450 | 451 | // now resize left (won't change left.len) 452 | left.resize(new_len + 1); 453 | 454 | // copy the contents of the other string 455 | memcpy(&left.str[left.len], right.str, right.len); 456 | 457 | // update left.len 458 | left.len = new_len; 459 | 460 | // add null terminator 461 | left.str[new_len] = '\0'; 462 | 463 | // transfer from left before it gets destroyed 464 | return std::move(left); 465 | } 466 | 467 | template 468 | inline string operator + (string const& left, T const& right) { 469 | return left + string(right); 470 | } 471 | 472 | template 473 | inline string operator + (T const& left, string const& right) { 474 | return string(left) + right; 475 | } 476 | 477 | template 478 | inline string operator + (string&& left, T const& right) { 479 | return std::forward(left) + string(right); 480 | } 481 | 482 | string operator + (string const& left, const char* right) { 483 | size_t right_len = strlen(right); 484 | 485 | // allocate sum of both string lengths + 1 for null terminator 486 | size_t new_len = left.len + right_len; 487 | // call private constructor to avoid copying 488 | string s(new_len, new_len + 1); 489 | 490 | // copy the contents of both strings 491 | memcpy(s.str, left.str, left.len); 492 | memcpy(&s.str[left.len], right, right_len); 493 | 494 | // add null terminator 495 | s.str[new_len] = '\0'; 496 | 497 | return s; 498 | } 499 | 500 | string operator + (const char* left, string const& right) { 501 | size_t left_len = strlen(left); 502 | 503 | // allocate sum of both string lengths + 1 for null terminator 504 | size_t new_len = left_len + right.len; 505 | // call private constructor to avoid copying 506 | string s(new_len, new_len + 1); 507 | 508 | // copy the contents of both strings 509 | memcpy(s.str, left, left_len); 510 | memcpy(&s.str[left_len], right.str, right.len); 511 | 512 | // add null terminator 513 | s.str[new_len] = '\0'; 514 | 515 | return s; 516 | } 517 | 518 | string operator + (string&& left, const char* right) { 519 | /* left is an rvalue, so we can just modify then move it 520 | to increase performance */ 521 | 522 | size_t right_len = strlen(right); 523 | 524 | // allocate sum of both string lengths + 1 for null terminator 525 | size_t new_len = left.len + right_len; 526 | 527 | // now resize left (won't change left.len) 528 | left.resize(new_len + 1); 529 | 530 | // copy the contents of the other string 531 | memcpy(&left.str[left.len], right, right_len); 532 | 533 | // update left.len 534 | left.len = new_len; 535 | 536 | // add null terminator 537 | left.str[new_len] = '\0'; 538 | 539 | // transfer from left before it gets destroyed 540 | return std::move(left); 541 | } 542 | 543 | // only works if using namespace mp, "string"_mp will be equal to string("string", 6) 544 | inline string operator""_mp(const char* value, unsigned long size) { 545 | return string(value, size); 546 | } 547 | 548 | }; 549 | 550 | #endif /* string_hpp */ 551 | --------------------------------------------------------------------------------