├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── images ├── bus.jpg ├── bus_out.jpg ├── zidane.jpg └── zidane_out.jpg ├── include ├── cxxopts.hpp ├── detector.h └── utils.h ├── src ├── detector.cpp └── main.cpp └── weights └── coco.names /.gitignore: -------------------------------------------------------------------------------- 1 | libtorch/ 2 | .idea/ 3 | bin/ 4 | build/ 5 | cmake-build-debug/ 6 | *.pt 7 | *.jpg 8 | 9 | # Prerequisites 10 | *.d 11 | 12 | # Compiled Object files 13 | *.slo 14 | *.lo 15 | *.o 16 | *.obj 17 | 18 | # Precompiled Headers 19 | *.gch 20 | *.pch 21 | 22 | # Compiled Dynamic libraries 23 | *.so 24 | *.dylib 25 | *.dll 26 | 27 | # Fortran module files 28 | *.mod 29 | *.smod 30 | 31 | # Compiled Static libraries 32 | *.lai 33 | *.la 34 | *.a 35 | *.lib 36 | 37 | # Executables 38 | *.exe 39 | *.out 40 | *.app 41 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5.1) 2 | project(libtorch-yolov5) 3 | 4 | set(CMAKE_CXX_STANDARD 14) 5 | # It prevents the decay to C++98 when the compiler does not support C++14 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | # It disables the use of compiler-specific extensions 8 | # e.g. -std=c++14 rather than -std=gnu++14 9 | set(CMAKE_CXX_EXTENSIONS OFF) 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") 11 | 12 | # Try to find OpenCV 13 | # set(OpenCV_DIR ....) 14 | find_package(OpenCV REQUIRED) 15 | if (OpenCV_FOUND) 16 | # If the package has been found, several variables will 17 | # be set, you can find the full list with descriptions 18 | # in the OpenCVConfig.cmake file. 19 | # Print some message showing some of them 20 | message(STATUS "OpenCV library status:") 21 | message(STATUS " version: ${OpenCV_VERSION}") 22 | message(STATUS " include path: ${OpenCV_INCLUDE_DIRS}" \n) 23 | else () 24 | message(FATAL_ERROR "Could not locate OpenCV" \n) 25 | endif() 26 | 27 | set(Torch_DIR libtorch/share/cmake/Torch) 28 | find_package(Torch PATHS ${Torch_DIR} NO_DEFAULT REQUIRED) 29 | if (Torch_FOUND) 30 | message(STATUS "Torch library found!") 31 | message(STATUS " include path: ${TORCH_INCLUDE_DIRS}" \n) 32 | else () 33 | message(FATAL_ERROR "Could not locate Torch" \n) 34 | endif() 35 | 36 | include_directories(${PROJECT_SOURCE_DIR}/include) 37 | 38 | file(GLOB SOURCE_FILES src/*.cpp) 39 | 40 | add_executable(${CMAKE_PROJECT_NAME} ${SOURCE_FILES}) 41 | 42 | target_link_libraries ( 43 | ${CMAKE_PROJECT_NAME} 44 | ${OpenCV_LIBS} 45 | ${TORCH_LIBRARIES} 46 | ) 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Yasen Hu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | A LibTorch inference implementation of the [yolov5](https://github.com/ultralytics/yolov5) object detection algorithm. Both GPU and CPU are supported. 4 | 5 | 6 | 7 | ## Dependencies 8 | 9 | - Ubuntu 16.04 10 | - CUDA 10.2 11 | - OpenCV 3.4.12 12 | - LibTorch 1.6.0 13 | 14 | 15 | 16 | ## TorchScript Model Export 17 | 18 | Please refer to the official document here: https://github.com/ultralytics/yolov5/issues/251 19 | 20 | 21 | 22 | **Mandatory Update**: developer needs to modify following code from the original [export.py in yolov5](https://github.com/ultralytics/yolov5/blob/master/models/export.py) 23 | 24 | ```bash 25 | # line 29 26 | model.model[-1].export = False 27 | ``` 28 | 29 | 30 | 31 | **Add GPU support**: Note that the current export script in [yolov5](https://github.com/ultralytics/yolov5) **uses CPU by default**, the "export.py" needs to be modified as following to support GPU: 32 | 33 | ```python 34 | # line 28 35 | img = torch.zeros((opt.batch_size, 3, *opt.img_size)).to(device='cuda') 36 | # line 31 37 | model = attempt_load(opt.weights, map_location=torch.device('cuda')) 38 | ``` 39 | 40 | 41 | 42 | Export a trained yolov5 model: 43 | 44 | ```bash 45 | cd yolov5 46 | export PYTHONPATH="$PWD" # add path 47 | python models/export.py --weights yolov5s.pt --img 640 --batch 1 # export 48 | ``` 49 | 50 | 51 | 52 | ## Setup 53 | 54 | ```bash 55 | $ cd /path/to/libtorch-yolo5 56 | $ wget https://download.pytorch.org/libtorch/cu102/libtorch-cxx11-abi-shared-with-deps-1.6.0.zip 57 | $ unzip libtorch-cxx11-abi-shared-with-deps-1.6.0.zip 58 | $ mkdir build && cd build 59 | $ cmake .. && make 60 | ``` 61 | 62 | 63 | 64 | To run inference on examples in the `./images` folder: 65 | 66 | ```bash 67 | # CPU 68 | $ ./libtorch-yolov5 --source ../images/bus.jpg --weights ../weights/yolov5s.torchscript.pt --view-img 69 | # GPU 70 | $ ./libtorch-yolov5 --source ../images/bus.jpg --weights ../weights/yolov5s.torchscript.pt --gpu --view-img 71 | # Profiling 72 | $ CUDA_LAUNCH_BLOCKING=1 ./libtorch-yolov5 --source ../images/bus.jpg --weights ../weights/yolov5s.torchscript.pt --gpu --view-img 73 | ``` 74 | 75 | 76 | 77 | ## Demo 78 | 79 | ![Bus](images/bus_out.jpg) 80 | 81 | 82 | 83 | ![Zidane](images/zidane_out.jpg) 84 | 85 | 86 | 87 | ## FAQ 88 | 89 | 1. terminate called after throwing an instance of 'c10::Error' what(): isTuple() INTERNAL ASSERT FAILED 90 | 91 | - Make sure "model.model[-1].export = False" when running export script. 92 | 93 | 2. Why the first "inference takes" so long from the log? 94 | 95 | - The first inference is slower as well due to the initial optimization that the JIT (Just-in-time compilation) is doing on your code. This is similar to "warm up" in other JIT compilers. Typically, production services will warm up a model using representative inputs before marking it as available. 96 | 97 | - It may take longer time for the first cycle. The [yolov5 python version](https://github.com/ultralytics/yolov5) run the inference once with an empty image before the actual detection pipeline. User can modify the code to process the same image multiple times or process a video to get the valid processing time. 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | ## References 106 | 107 | 1. https://github.com/ultralytics/yolov5 108 | 2. [Question about the code in non_max_suppression](https://github.com/ultralytics/yolov5/issues/422) 109 | 3. https://github.com/walktree/libtorch-yolov3 110 | 4. https://pytorch.org/cppdocs/index.html 111 | 5. https://github.com/pytorch/vision 112 | 6. [PyTorch.org - CUDA SEMANTICS](https://pytorch.org/docs/stable/notes/cuda.html) 113 | 7. [PyTorch.org - add synchronization points](https://discuss.pytorch.org/t/why-is-the-const-time-with-fp32-and-fp16-almost-the-same-in-libtorchs-forward/45792/5) 114 | 8. [PyTorch - why first inference is slower](https://github.com/pytorch/pytorch/issues/2694) 115 | 116 | -------------------------------------------------------------------------------- /images/bus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasenh/libtorch-yolov5/2ff39dc39b988434b0e2921d5269eff2afcf77f5/images/bus.jpg -------------------------------------------------------------------------------- /images/bus_out.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasenh/libtorch-yolov5/2ff39dc39b988434b0e2921d5269eff2afcf77f5/images/bus_out.jpg -------------------------------------------------------------------------------- /images/zidane.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasenh/libtorch-yolov5/2ff39dc39b988434b0e2921d5269eff2afcf77f5/images/zidane.jpg -------------------------------------------------------------------------------- /images/zidane_out.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasenh/libtorch-yolov5/2ff39dc39b988434b0e2921d5269eff2afcf77f5/images/zidane_out.jpg -------------------------------------------------------------------------------- /include/cxxopts.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | */ 24 | 25 | #ifndef CXXOPTS_HPP_INCLUDED 26 | #define CXXOPTS_HPP_INCLUDED 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #ifdef __cpp_lib_optional 44 | #include 45 | #define CXXOPTS_HAS_OPTIONAL 46 | #endif 47 | 48 | #if __cplusplus >= 201603L 49 | #define CXXOPTS_NODISCARD [[nodiscard]] 50 | #else 51 | #define CXXOPTS_NODISCARD 52 | #endif 53 | 54 | #ifndef CXXOPTS_VECTOR_DELIMITER 55 | #define CXXOPTS_VECTOR_DELIMITER ',' 56 | #endif 57 | 58 | #define CXXOPTS__VERSION_MAJOR 2 59 | #define CXXOPTS__VERSION_MINOR 2 60 | #define CXXOPTS__VERSION_PATCH 0 61 | 62 | namespace cxxopts 63 | { 64 | static constexpr struct { 65 | uint8_t major, minor, patch; 66 | } version = { 67 | CXXOPTS__VERSION_MAJOR, 68 | CXXOPTS__VERSION_MINOR, 69 | CXXOPTS__VERSION_PATCH 70 | }; 71 | } // namespace cxxopts 72 | 73 | //when we ask cxxopts to use Unicode, help strings are processed using ICU, 74 | //which results in the correct lengths being computed for strings when they 75 | //are formatted for the help output 76 | //it is necessary to make sure that can be found by the 77 | //compiler, and that icu-uc is linked in to the binary. 78 | 79 | #ifdef CXXOPTS_USE_UNICODE 80 | #include 81 | 82 | namespace cxxopts 83 | { 84 | typedef icu::UnicodeString String; 85 | 86 | inline 87 | String 88 | toLocalString(std::string s) 89 | { 90 | return icu::UnicodeString::fromUTF8(std::move(s)); 91 | } 92 | 93 | class UnicodeStringIterator : public 94 | std::iterator 95 | { 96 | public: 97 | 98 | UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) 99 | : s(string) 100 | , i(pos) 101 | { 102 | } 103 | 104 | value_type 105 | operator*() const 106 | { 107 | return s->char32At(i); 108 | } 109 | 110 | bool 111 | operator==(const UnicodeStringIterator& rhs) const 112 | { 113 | return s == rhs.s && i == rhs.i; 114 | } 115 | 116 | bool 117 | operator!=(const UnicodeStringIterator& rhs) const 118 | { 119 | return !(*this == rhs); 120 | } 121 | 122 | UnicodeStringIterator& 123 | operator++() 124 | { 125 | ++i; 126 | return *this; 127 | } 128 | 129 | UnicodeStringIterator 130 | operator+(int32_t v) 131 | { 132 | return UnicodeStringIterator(s, i + v); 133 | } 134 | 135 | private: 136 | const icu::UnicodeString* s; 137 | int32_t i; 138 | }; 139 | 140 | inline 141 | String& 142 | stringAppend(String&s, String a) 143 | { 144 | return s.append(std::move(a)); 145 | } 146 | 147 | inline 148 | String& 149 | stringAppend(String& s, int n, UChar32 c) 150 | { 151 | for (int i = 0; i != n; ++i) 152 | { 153 | s.append(c); 154 | } 155 | 156 | return s; 157 | } 158 | 159 | template 160 | String& 161 | stringAppend(String& s, Iterator begin, Iterator end) 162 | { 163 | while (begin != end) 164 | { 165 | s.append(*begin); 166 | ++begin; 167 | } 168 | 169 | return s; 170 | } 171 | 172 | inline 173 | size_t 174 | stringLength(const String& s) 175 | { 176 | return s.length(); 177 | } 178 | 179 | inline 180 | std::string 181 | toUTF8String(const String& s) 182 | { 183 | std::string result; 184 | s.toUTF8String(result); 185 | 186 | return result; 187 | } 188 | 189 | inline 190 | bool 191 | empty(const String& s) 192 | { 193 | return s.isEmpty(); 194 | } 195 | } 196 | 197 | namespace std 198 | { 199 | inline 200 | cxxopts::UnicodeStringIterator 201 | begin(const icu::UnicodeString& s) 202 | { 203 | return cxxopts::UnicodeStringIterator(&s, 0); 204 | } 205 | 206 | inline 207 | cxxopts::UnicodeStringIterator 208 | end(const icu::UnicodeString& s) 209 | { 210 | return cxxopts::UnicodeStringIterator(&s, s.length()); 211 | } 212 | } 213 | 214 | //ifdef CXXOPTS_USE_UNICODE 215 | #else 216 | 217 | namespace cxxopts 218 | { 219 | typedef std::string String; 220 | 221 | template 222 | T 223 | toLocalString(T&& t) 224 | { 225 | return std::forward(t); 226 | } 227 | 228 | inline 229 | size_t 230 | stringLength(const String& s) 231 | { 232 | return s.length(); 233 | } 234 | 235 | inline 236 | String& 237 | stringAppend(String&s, const String& a) 238 | { 239 | return s.append(a); 240 | } 241 | 242 | inline 243 | String& 244 | stringAppend(String& s, size_t n, char c) 245 | { 246 | return s.append(n, c); 247 | } 248 | 249 | template 250 | String& 251 | stringAppend(String& s, Iterator begin, Iterator end) 252 | { 253 | return s.append(begin, end); 254 | } 255 | 256 | template 257 | std::string 258 | toUTF8String(T&& t) 259 | { 260 | return std::forward(t); 261 | } 262 | 263 | inline 264 | bool 265 | empty(const std::string& s) 266 | { 267 | return s.empty(); 268 | } 269 | } // namespace cxxopts 270 | 271 | //ifdef CXXOPTS_USE_UNICODE 272 | #endif 273 | 274 | namespace cxxopts 275 | { 276 | namespace 277 | { 278 | #ifdef _WIN32 279 | const std::string LQUOTE("\'"); 280 | const std::string RQUOTE("\'"); 281 | #else 282 | const std::string LQUOTE("‘"); 283 | const std::string RQUOTE("’"); 284 | #endif 285 | } // namespace 286 | 287 | class Value : public std::enable_shared_from_this 288 | { 289 | public: 290 | 291 | virtual ~Value() = default; 292 | 293 | virtual 294 | std::shared_ptr 295 | clone() const = 0; 296 | 297 | virtual void 298 | parse(const std::string& text) const = 0; 299 | 300 | virtual void 301 | parse() const = 0; 302 | 303 | virtual bool 304 | has_default() const = 0; 305 | 306 | virtual bool 307 | is_container() const = 0; 308 | 309 | virtual bool 310 | has_implicit() const = 0; 311 | 312 | virtual std::string 313 | get_default_value() const = 0; 314 | 315 | virtual std::string 316 | get_implicit_value() const = 0; 317 | 318 | virtual std::shared_ptr 319 | default_value(const std::string& value) = 0; 320 | 321 | virtual std::shared_ptr 322 | implicit_value(const std::string& value) = 0; 323 | 324 | virtual std::shared_ptr 325 | no_implicit_value() = 0; 326 | 327 | virtual bool 328 | is_boolean() const = 0; 329 | }; 330 | 331 | class OptionException : public std::exception 332 | { 333 | public: 334 | explicit OptionException(std::string message) 335 | : m_message(std::move(message)) 336 | { 337 | } 338 | 339 | CXXOPTS_NODISCARD 340 | const char* 341 | what() const noexcept override 342 | { 343 | return m_message.c_str(); 344 | } 345 | 346 | private: 347 | std::string m_message; 348 | }; 349 | 350 | class OptionSpecException : public OptionException 351 | { 352 | public: 353 | 354 | explicit OptionSpecException(const std::string& message) 355 | : OptionException(message) 356 | { 357 | } 358 | }; 359 | 360 | class OptionParseException : public OptionException 361 | { 362 | public: 363 | explicit OptionParseException(const std::string& message) 364 | : OptionException(message) 365 | { 366 | } 367 | }; 368 | 369 | class option_exists_error : public OptionSpecException 370 | { 371 | public: 372 | explicit option_exists_error(const std::string& option) 373 | : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") 374 | { 375 | } 376 | }; 377 | 378 | class invalid_option_format_error : public OptionSpecException 379 | { 380 | public: 381 | explicit invalid_option_format_error(const std::string& format) 382 | : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) 383 | { 384 | } 385 | }; 386 | 387 | class option_syntax_exception : public OptionParseException { 388 | public: 389 | explicit option_syntax_exception(const std::string& text) 390 | : OptionParseException("Argument " + LQUOTE + text + RQUOTE + 391 | " starts with a - but has incorrect syntax") 392 | { 393 | } 394 | }; 395 | 396 | class option_not_exists_exception : public OptionParseException 397 | { 398 | public: 399 | explicit option_not_exists_exception(const std::string& option) 400 | : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") 401 | { 402 | } 403 | }; 404 | 405 | class missing_argument_exception : public OptionParseException 406 | { 407 | public: 408 | explicit missing_argument_exception(const std::string& option) 409 | : OptionParseException( 410 | "Option " + LQUOTE + option + RQUOTE + " is missing an argument" 411 | ) 412 | { 413 | } 414 | }; 415 | 416 | class option_requires_argument_exception : public OptionParseException 417 | { 418 | public: 419 | explicit option_requires_argument_exception(const std::string& option) 420 | : OptionParseException( 421 | "Option " + LQUOTE + option + RQUOTE + " requires an argument" 422 | ) 423 | { 424 | } 425 | }; 426 | 427 | class option_not_has_argument_exception : public OptionParseException 428 | { 429 | public: 430 | option_not_has_argument_exception 431 | ( 432 | const std::string& option, 433 | const std::string& arg 434 | ) 435 | : OptionParseException( 436 | "Option " + LQUOTE + option + RQUOTE + 437 | " does not take an argument, but argument " + 438 | LQUOTE + arg + RQUOTE + " given" 439 | ) 440 | { 441 | } 442 | }; 443 | 444 | class option_not_present_exception : public OptionParseException 445 | { 446 | public: 447 | explicit option_not_present_exception(const std::string& option) 448 | : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") 449 | { 450 | } 451 | }; 452 | 453 | class option_has_no_value_exception : public OptionException 454 | { 455 | public: 456 | explicit option_has_no_value_exception(const std::string& option) 457 | : OptionException( 458 | option.empty() ? 459 | ("Option " + LQUOTE + option + RQUOTE + " has no value") : 460 | "Option has no value") 461 | { 462 | } 463 | }; 464 | 465 | class argument_incorrect_type : public OptionParseException 466 | { 467 | public: 468 | explicit argument_incorrect_type 469 | ( 470 | const std::string& arg 471 | ) 472 | : OptionParseException( 473 | "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" 474 | ) 475 | { 476 | } 477 | }; 478 | 479 | class option_required_exception : public OptionParseException 480 | { 481 | public: 482 | explicit option_required_exception(const std::string& option) 483 | : OptionParseException( 484 | "Option " + LQUOTE + option + RQUOTE + " is required but not present" 485 | ) 486 | { 487 | } 488 | }; 489 | 490 | template 491 | void throw_or_mimic(const std::string& text) 492 | { 493 | static_assert(std::is_base_of::value, 494 | "throw_or_mimic only works on std::exception and " 495 | "deriving classes"); 496 | 497 | #ifndef CXXOPTS_NO_EXCEPTIONS 498 | // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw 499 | throw T{text}; 500 | #else 501 | // Otherwise manually instantiate the exception, print what() to stderr, 502 | // and exit 503 | T exception{text}; 504 | std::cerr << exception.what() << std::endl; 505 | std::exit(EXIT_FAILURE); 506 | #endif 507 | } 508 | 509 | namespace values 510 | { 511 | namespace 512 | { 513 | std::basic_regex integer_pattern 514 | ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); 515 | std::basic_regex truthy_pattern 516 | ("(t|T)(rue)?|1"); 517 | std::basic_regex falsy_pattern 518 | ("(f|F)(alse)?|0"); 519 | } // namespace 520 | 521 | namespace detail 522 | { 523 | template 524 | struct SignedCheck; 525 | 526 | template 527 | struct SignedCheck 528 | { 529 | template 530 | void 531 | operator()(bool negative, U u, const std::string& text) 532 | { 533 | if (negative) 534 | { 535 | if (u > static_cast((std::numeric_limits::min)())) 536 | { 537 | throw_or_mimic(text); 538 | } 539 | } 540 | else 541 | { 542 | if (u > static_cast((std::numeric_limits::max)())) 543 | { 544 | throw_or_mimic(text); 545 | } 546 | } 547 | } 548 | }; 549 | 550 | template 551 | struct SignedCheck 552 | { 553 | template 554 | void 555 | operator()(bool, U, const std::string&) {} 556 | }; 557 | 558 | template 559 | void 560 | check_signed_range(bool negative, U value, const std::string& text) 561 | { 562 | SignedCheck::is_signed>()(negative, value, text); 563 | } 564 | } // namespace detail 565 | 566 | template 567 | R 568 | checked_negate(T&& t, const std::string&, std::true_type) 569 | { 570 | // if we got to here, then `t` is a positive number that fits into 571 | // `R`. So to avoid MSVC C4146, we first cast it to `R`. 572 | // See https://github.com/jarro2783/cxxopts/issues/62 for more details. 573 | return static_cast(-static_cast(t-1)-1); 574 | } 575 | 576 | template 577 | T 578 | checked_negate(T&& t, const std::string& text, std::false_type) 579 | { 580 | throw_or_mimic(text); 581 | return t; 582 | } 583 | 584 | template 585 | void 586 | integer_parser(const std::string& text, T& value) 587 | { 588 | std::smatch match; 589 | std::regex_match(text, match, integer_pattern); 590 | 591 | if (match.length() == 0) 592 | { 593 | throw_or_mimic(text); 594 | } 595 | 596 | if (match.length(4) > 0) 597 | { 598 | value = 0; 599 | return; 600 | } 601 | 602 | using US = typename std::make_unsigned::type; 603 | 604 | constexpr bool is_signed = std::numeric_limits::is_signed; 605 | const bool negative = match.length(1) > 0; 606 | const uint8_t base = match.length(2) > 0 ? 16 : 10; 607 | 608 | auto value_match = match[3]; 609 | 610 | US result = 0; 611 | 612 | for (auto iter = value_match.first; iter != value_match.second; ++iter) 613 | { 614 | US digit = 0; 615 | 616 | if (*iter >= '0' && *iter <= '9') 617 | { 618 | digit = static_cast(*iter - '0'); 619 | } 620 | else if (base == 16 && *iter >= 'a' && *iter <= 'f') 621 | { 622 | digit = static_cast(*iter - 'a' + 10); 623 | } 624 | else if (base == 16 && *iter >= 'A' && *iter <= 'F') 625 | { 626 | digit = static_cast(*iter - 'A' + 10); 627 | } 628 | else 629 | { 630 | throw_or_mimic(text); 631 | } 632 | 633 | const US next = static_cast(result * base + digit); 634 | if (result > next) 635 | { 636 | throw_or_mimic(text); 637 | } 638 | 639 | result = next; 640 | } 641 | 642 | detail::check_signed_range(negative, result, text); 643 | 644 | if (negative) 645 | { 646 | value = checked_negate(result, 647 | text, 648 | std::integral_constant()); 649 | } 650 | else 651 | { 652 | value = static_cast(result); 653 | } 654 | } 655 | 656 | template 657 | void stringstream_parser(const std::string& text, T& value) 658 | { 659 | std::stringstream in(text); 660 | in >> value; 661 | if (!in) { 662 | throw_or_mimic(text); 663 | } 664 | } 665 | 666 | inline 667 | void 668 | parse_value(const std::string& text, uint8_t& value) 669 | { 670 | integer_parser(text, value); 671 | } 672 | 673 | inline 674 | void 675 | parse_value(const std::string& text, int8_t& value) 676 | { 677 | integer_parser(text, value); 678 | } 679 | 680 | inline 681 | void 682 | parse_value(const std::string& text, uint16_t& value) 683 | { 684 | integer_parser(text, value); 685 | } 686 | 687 | inline 688 | void 689 | parse_value(const std::string& text, int16_t& value) 690 | { 691 | integer_parser(text, value); 692 | } 693 | 694 | inline 695 | void 696 | parse_value(const std::string& text, uint32_t& value) 697 | { 698 | integer_parser(text, value); 699 | } 700 | 701 | inline 702 | void 703 | parse_value(const std::string& text, int32_t& value) 704 | { 705 | integer_parser(text, value); 706 | } 707 | 708 | inline 709 | void 710 | parse_value(const std::string& text, uint64_t& value) 711 | { 712 | integer_parser(text, value); 713 | } 714 | 715 | inline 716 | void 717 | parse_value(const std::string& text, int64_t& value) 718 | { 719 | integer_parser(text, value); 720 | } 721 | 722 | inline 723 | void 724 | parse_value(const std::string& text, bool& value) 725 | { 726 | std::smatch result; 727 | std::regex_match(text, result, truthy_pattern); 728 | 729 | if (!result.empty()) 730 | { 731 | value = true; 732 | return; 733 | } 734 | 735 | std::regex_match(text, result, falsy_pattern); 736 | if (!result.empty()) 737 | { 738 | value = false; 739 | return; 740 | } 741 | 742 | throw_or_mimic(text); 743 | } 744 | 745 | inline 746 | void 747 | parse_value(const std::string& text, std::string& value) 748 | { 749 | value = text; 750 | } 751 | 752 | // The fallback parser. It uses the stringstream parser to parse all types 753 | // that have not been overloaded explicitly. It has to be placed in the 754 | // source code before all other more specialized templates. 755 | template 756 | void 757 | parse_value(const std::string& text, T& value) { 758 | stringstream_parser(text, value); 759 | } 760 | 761 | template 762 | void 763 | parse_value(const std::string& text, std::vector& value) 764 | { 765 | std::stringstream in(text); 766 | std::string token; 767 | while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { 768 | T v; 769 | parse_value(token, v); 770 | value.emplace_back(std::move(v)); 771 | } 772 | } 773 | 774 | #ifdef CXXOPTS_HAS_OPTIONAL 775 | template 776 | void 777 | parse_value(const std::string& text, std::optional& value) 778 | { 779 | T result; 780 | parse_value(text, result); 781 | value = std::move(result); 782 | } 783 | #endif 784 | 785 | inline 786 | void parse_value(const std::string& text, char& c) 787 | { 788 | if (text.length() != 1) 789 | { 790 | throw_or_mimic(text); 791 | } 792 | 793 | c = text[0]; 794 | } 795 | 796 | template 797 | struct type_is_container 798 | { 799 | static constexpr bool value = false; 800 | }; 801 | 802 | template 803 | struct type_is_container> 804 | { 805 | static constexpr bool value = true; 806 | }; 807 | 808 | template 809 | class abstract_value : public Value 810 | { 811 | using Self = abstract_value; 812 | 813 | public: 814 | abstract_value() 815 | : m_result(std::make_shared()) 816 | , m_store(m_result.get()) 817 | { 818 | } 819 | 820 | explicit abstract_value(T* t) 821 | : m_store(t) 822 | { 823 | } 824 | 825 | ~abstract_value() override = default; 826 | 827 | abstract_value(const abstract_value& rhs) 828 | { 829 | if (rhs.m_result) 830 | { 831 | m_result = std::make_shared(); 832 | m_store = m_result.get(); 833 | } 834 | else 835 | { 836 | m_store = rhs.m_store; 837 | } 838 | 839 | m_default = rhs.m_default; 840 | m_implicit = rhs.m_implicit; 841 | m_default_value = rhs.m_default_value; 842 | m_implicit_value = rhs.m_implicit_value; 843 | } 844 | 845 | void 846 | parse(const std::string& text) const override 847 | { 848 | parse_value(text, *m_store); 849 | } 850 | 851 | bool 852 | is_container() const override 853 | { 854 | return type_is_container::value; 855 | } 856 | 857 | void 858 | parse() const override 859 | { 860 | parse_value(m_default_value, *m_store); 861 | } 862 | 863 | bool 864 | has_default() const override 865 | { 866 | return m_default; 867 | } 868 | 869 | bool 870 | has_implicit() const override 871 | { 872 | return m_implicit; 873 | } 874 | 875 | std::shared_ptr 876 | default_value(const std::string& value) override 877 | { 878 | m_default = true; 879 | m_default_value = value; 880 | return shared_from_this(); 881 | } 882 | 883 | std::shared_ptr 884 | implicit_value(const std::string& value) override 885 | { 886 | m_implicit = true; 887 | m_implicit_value = value; 888 | return shared_from_this(); 889 | } 890 | 891 | std::shared_ptr 892 | no_implicit_value() override 893 | { 894 | m_implicit = false; 895 | return shared_from_this(); 896 | } 897 | 898 | std::string 899 | get_default_value() const override 900 | { 901 | return m_default_value; 902 | } 903 | 904 | std::string 905 | get_implicit_value() const override 906 | { 907 | return m_implicit_value; 908 | } 909 | 910 | bool 911 | is_boolean() const override 912 | { 913 | return std::is_same::value; 914 | } 915 | 916 | const T& 917 | get() const 918 | { 919 | if (m_store == nullptr) 920 | { 921 | return *m_result; 922 | } 923 | return *m_store; 924 | } 925 | 926 | protected: 927 | std::shared_ptr m_result; 928 | T* m_store; 929 | 930 | bool m_default = false; 931 | bool m_implicit = false; 932 | 933 | std::string m_default_value; 934 | std::string m_implicit_value; 935 | }; 936 | 937 | template 938 | class standard_value : public abstract_value 939 | { 940 | public: 941 | using abstract_value::abstract_value; 942 | 943 | CXXOPTS_NODISCARD 944 | std::shared_ptr 945 | clone() const 946 | { 947 | return std::make_shared>(*this); 948 | } 949 | }; 950 | 951 | template <> 952 | class standard_value : public abstract_value 953 | { 954 | public: 955 | ~standard_value() override = default; 956 | 957 | standard_value() 958 | { 959 | set_default_and_implicit(); 960 | } 961 | 962 | explicit standard_value(bool* b) 963 | : abstract_value(b) 964 | { 965 | set_default_and_implicit(); 966 | } 967 | 968 | std::shared_ptr 969 | clone() const override 970 | { 971 | return std::make_shared>(*this); 972 | } 973 | 974 | private: 975 | 976 | void 977 | set_default_and_implicit() 978 | { 979 | m_default = true; 980 | m_default_value = "false"; 981 | m_implicit = true; 982 | m_implicit_value = "true"; 983 | } 984 | }; 985 | } // namespace values 986 | 987 | template 988 | std::shared_ptr 989 | value() 990 | { 991 | return std::make_shared>(); 992 | } 993 | 994 | template 995 | std::shared_ptr 996 | value(T& t) 997 | { 998 | return std::make_shared>(&t); 999 | } 1000 | 1001 | class OptionAdder; 1002 | 1003 | class OptionDetails 1004 | { 1005 | public: 1006 | OptionDetails 1007 | ( 1008 | std::string short_, 1009 | std::string long_, 1010 | String desc, 1011 | std::shared_ptr val 1012 | ) 1013 | : m_short(std::move(short_)) 1014 | , m_long(std::move(long_)) 1015 | , m_desc(std::move(desc)) 1016 | , m_value(std::move(val)) 1017 | , m_count(0) 1018 | { 1019 | } 1020 | 1021 | OptionDetails(const OptionDetails& rhs) 1022 | : m_desc(rhs.m_desc) 1023 | , m_count(rhs.m_count) 1024 | { 1025 | m_value = rhs.m_value->clone(); 1026 | } 1027 | 1028 | OptionDetails(OptionDetails&& rhs) = default; 1029 | 1030 | CXXOPTS_NODISCARD 1031 | const String& 1032 | description() const 1033 | { 1034 | return m_desc; 1035 | } 1036 | 1037 | CXXOPTS_NODISCARD 1038 | const Value& 1039 | value() const { 1040 | return *m_value; 1041 | } 1042 | 1043 | CXXOPTS_NODISCARD 1044 | std::shared_ptr 1045 | make_storage() const 1046 | { 1047 | return m_value->clone(); 1048 | } 1049 | 1050 | CXXOPTS_NODISCARD 1051 | const std::string& 1052 | short_name() const 1053 | { 1054 | return m_short; 1055 | } 1056 | 1057 | CXXOPTS_NODISCARD 1058 | const std::string& 1059 | long_name() const 1060 | { 1061 | return m_long; 1062 | } 1063 | 1064 | private: 1065 | std::string m_short; 1066 | std::string m_long; 1067 | String m_desc; 1068 | std::shared_ptr m_value; 1069 | int m_count; 1070 | }; 1071 | 1072 | struct HelpOptionDetails 1073 | { 1074 | std::string s; 1075 | std::string l; 1076 | String desc; 1077 | bool has_default; 1078 | std::string default_value; 1079 | bool has_implicit; 1080 | std::string implicit_value; 1081 | std::string arg_help; 1082 | bool is_container; 1083 | bool is_boolean; 1084 | }; 1085 | 1086 | struct HelpGroupDetails 1087 | { 1088 | std::string name; 1089 | std::string description; 1090 | std::vector options; 1091 | }; 1092 | 1093 | class OptionValue 1094 | { 1095 | public: 1096 | void 1097 | parse 1098 | ( 1099 | const std::shared_ptr& details, 1100 | const std::string& text 1101 | ) 1102 | { 1103 | ensure_value(details); 1104 | ++m_count; 1105 | m_value->parse(text); 1106 | m_long_name = &details->long_name(); 1107 | } 1108 | 1109 | void 1110 | parse_default(const std::shared_ptr& details) 1111 | { 1112 | ensure_value(details); 1113 | m_default = true; 1114 | m_long_name = &details->long_name(); 1115 | m_value->parse(); 1116 | } 1117 | 1118 | CXXOPTS_NODISCARD 1119 | size_t 1120 | count() const noexcept 1121 | { 1122 | return m_count; 1123 | } 1124 | 1125 | // TODO: maybe default options should count towards the number of arguments 1126 | CXXOPTS_NODISCARD 1127 | bool 1128 | has_default() const noexcept 1129 | { 1130 | return m_default; 1131 | } 1132 | 1133 | template 1134 | const T& 1135 | as() const 1136 | { 1137 | if (m_value == nullptr) { 1138 | throw_or_mimic( 1139 | m_long_name == nullptr ? "" : *m_long_name); 1140 | } 1141 | 1142 | #ifdef CXXOPTS_NO_RTTI 1143 | return static_cast&>(*m_value).get(); 1144 | #else 1145 | return dynamic_cast&>(*m_value).get(); 1146 | #endif 1147 | } 1148 | 1149 | private: 1150 | void 1151 | ensure_value(const std::shared_ptr& details) 1152 | { 1153 | if (m_value == nullptr) 1154 | { 1155 | m_value = details->make_storage(); 1156 | } 1157 | } 1158 | 1159 | const std::string* m_long_name = nullptr; 1160 | // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, 1161 | // where the key has the string we point to. 1162 | std::shared_ptr m_value; 1163 | size_t m_count = 0; 1164 | bool m_default = false; 1165 | }; 1166 | 1167 | class KeyValue 1168 | { 1169 | public: 1170 | KeyValue(std::string key_, std::string value_) 1171 | : m_key(std::move(key_)) 1172 | , m_value(std::move(value_)) 1173 | { 1174 | } 1175 | 1176 | CXXOPTS_NODISCARD 1177 | const std::string& 1178 | key() const 1179 | { 1180 | return m_key; 1181 | } 1182 | 1183 | CXXOPTS_NODISCARD 1184 | const std::string& 1185 | value() const 1186 | { 1187 | return m_value; 1188 | } 1189 | 1190 | template 1191 | T 1192 | as() const 1193 | { 1194 | T result; 1195 | values::parse_value(m_value, result); 1196 | return result; 1197 | } 1198 | 1199 | private: 1200 | std::string m_key; 1201 | std::string m_value; 1202 | }; 1203 | 1204 | class ParseResult 1205 | { 1206 | public: 1207 | 1208 | ParseResult( 1209 | std::shared_ptr< 1210 | std::unordered_map> 1211 | >, 1212 | std::vector, 1213 | bool allow_unrecognised, 1214 | int&, const char**&); 1215 | 1216 | size_t 1217 | count(const std::string& o) const 1218 | { 1219 | auto iter = m_options->find(o); 1220 | if (iter == m_options->end()) 1221 | { 1222 | return 0; 1223 | } 1224 | 1225 | auto riter = m_results.find(iter->second); 1226 | 1227 | return riter->second.count(); 1228 | } 1229 | 1230 | const OptionValue& 1231 | operator[](const std::string& option) const 1232 | { 1233 | auto iter = m_options->find(option); 1234 | 1235 | if (iter == m_options->end()) 1236 | { 1237 | throw_or_mimic(option); 1238 | } 1239 | 1240 | auto riter = m_results.find(iter->second); 1241 | 1242 | return riter->second; 1243 | } 1244 | 1245 | const std::vector& 1246 | arguments() const 1247 | { 1248 | return m_sequential; 1249 | } 1250 | 1251 | private: 1252 | 1253 | void 1254 | parse(int& argc, const char**& argv); 1255 | 1256 | void 1257 | add_to_option(const std::string& option, const std::string& arg); 1258 | 1259 | bool 1260 | consume_positional(const std::string& a); 1261 | 1262 | void 1263 | parse_option 1264 | ( 1265 | const std::shared_ptr& value, 1266 | const std::string& name, 1267 | const std::string& arg = "" 1268 | ); 1269 | 1270 | void 1271 | parse_default(const std::shared_ptr& details); 1272 | 1273 | void 1274 | checked_parse_arg 1275 | ( 1276 | int argc, 1277 | const char* argv[], 1278 | int& current, 1279 | const std::shared_ptr& value, 1280 | const std::string& name 1281 | ); 1282 | 1283 | const std::shared_ptr< 1284 | std::unordered_map> 1285 | > m_options; 1286 | std::vector m_positional; 1287 | std::vector::iterator m_next_positional; 1288 | std::unordered_set m_positional_set; 1289 | std::unordered_map, OptionValue> m_results; 1290 | 1291 | bool m_allow_unrecognised; 1292 | 1293 | std::vector m_sequential; 1294 | }; 1295 | 1296 | struct Option 1297 | { 1298 | Option 1299 | ( 1300 | std::string opts, 1301 | std::string desc, 1302 | std::shared_ptr value = ::cxxopts::value(), 1303 | std::string arg_help = "" 1304 | ) 1305 | : opts_(std::move(opts)) 1306 | , desc_(std::move(desc)) 1307 | , value_(std::move(value)) 1308 | , arg_help_(std::move(arg_help)) 1309 | { 1310 | } 1311 | 1312 | std::string opts_; 1313 | std::string desc_; 1314 | std::shared_ptr value_; 1315 | std::string arg_help_; 1316 | }; 1317 | 1318 | class Options 1319 | { 1320 | using OptionMap = std::unordered_map>; 1321 | public: 1322 | 1323 | explicit Options(std::string program, std::string help_string = "") 1324 | : m_program(std::move(program)) 1325 | , m_help_string(toLocalString(std::move(help_string))) 1326 | , m_custom_help("[OPTION...]") 1327 | , m_positional_help("positional parameters") 1328 | , m_show_positional(false) 1329 | , m_allow_unrecognised(false) 1330 | , m_options(std::make_shared()) 1331 | , m_next_positional(m_positional.end()) 1332 | { 1333 | } 1334 | 1335 | Options& 1336 | positional_help(std::string help_text) 1337 | { 1338 | m_positional_help = std::move(help_text); 1339 | return *this; 1340 | } 1341 | 1342 | Options& 1343 | custom_help(std::string help_text) 1344 | { 1345 | m_custom_help = std::move(help_text); 1346 | return *this; 1347 | } 1348 | 1349 | Options& 1350 | show_positional_help() 1351 | { 1352 | m_show_positional = true; 1353 | return *this; 1354 | } 1355 | 1356 | Options& 1357 | allow_unrecognised_options() 1358 | { 1359 | m_allow_unrecognised = true; 1360 | return *this; 1361 | } 1362 | 1363 | ParseResult 1364 | parse(int& argc, const char**& argv); 1365 | 1366 | OptionAdder 1367 | add_options(std::string group = ""); 1368 | 1369 | void 1370 | add_options 1371 | ( 1372 | const std::string& group, 1373 | std::initializer_list