Revision eee13fa3
Added by david.sorber over 2 years ago
| CMakeLists.txt | ||
|---|---|---|
|
cmake_minimum_required(VERSION 3.22)
|
||
|
project("copytool")
|
||
|
cmake_policy(SET CMP0054 NEW)
|
||
|
CMAKE_MINIMUM_REQUIRED(VERSION 3.22)
|
||
|
PROJECT("copytool")
|
||
|
CMAKE_POLICY(SET CMP0054 NEW)
|
||
|
|
||
|
|
||
|
################################################################################
|
||
| ... | ... | |
|
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter")
|
||
|
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter")
|
||
|
|
||
|
SET(COMPILE_FLAGS "-g -O3 -march=haswell -mtune=generic")
|
||
|
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMPILE_FLAGS}")
|
||
|
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMPILE_FLAGS}")
|
||
|
|
||
|
# Set the library install path depending on the Linux flavor
|
||
|
SET(LIBRARY_INSTALL_PATH "")
|
||
|
IF (DISTRIB_ID STREQUAL "Ubuntu")
|
||
| ... | ... | |
|
# Target: copytool
|
||
|
################################################################################
|
||
|
|
||
|
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/external)
|
||
|
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||
|
|
||
|
SET(copytool_sources
|
||
| external/CLI11.hpp | ||
|---|---|---|
|
// CLI11: Version 2.3.2
|
||
|
// Originally designed by Henry Schreiner
|
||
|
// https://github.com/CLIUtils/CLI11
|
||
|
//
|
||
|
// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts
|
||
|
// from: v2.3.2
|
||
|
//
|
||
|
// CLI11 2.3.2 Copyright (c) 2017-2022 University of Cincinnati, developed by Henry
|
||
|
// Schreiner under NSF AWARD 1414736. All rights reserved.
|
||
|
//
|
||
|
// Redistribution and use in source and binary forms of CLI11, 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.
|
||
|
|
||
|
#pragma once
|
||
|
|
||
|
// Standard combined includes:
|
||
|
#include <iomanip>
|
||
|
#include <set>
|
||
|
#include <memory>
|
||
|
#include <vector>
|
||
|
#include <utility>
|
||
|
#include <stdexcept>
|
||
|
#include <locale>
|
||
|
#include <functional>
|
||
|
#include <iostream>
|
||
|
#include <iterator>
|
||
|
#include <exception>
|
||
|
#include <numeric>
|
||
|
#include <fstream>
|
||
|
#include <string>
|
||
|
#include <type_traits>
|
||
|
#include <tuple>
|
||
|
#include <map>
|
||
|
#include <algorithm>
|
||
|
#include <cstdint>
|
||
|
#include <sstream>
|
||
|
#include <cmath>
|
||
|
#include <limits>
|
||
|
|
||
|
|
||
|
#define CLI11_VERSION_MAJOR 2
|
||
|
#define CLI11_VERSION_MINOR 3
|
||
|
#define CLI11_VERSION_PATCH 2
|
||
|
#define CLI11_VERSION "2.3.2"
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// The following version macro is very similar to the one in pybind11
|
||
|
#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER)
|
||
|
#if __cplusplus >= 201402L
|
||
|
#define CLI11_CPP14
|
||
|
#if __cplusplus >= 201703L
|
||
|
#define CLI11_CPP17
|
||
|
#if __cplusplus > 201703L
|
||
|
#define CLI11_CPP20
|
||
|
#endif
|
||
|
#endif
|
||
|
#endif
|
||
|
#elif defined(_MSC_VER) && __cplusplus == 199711L
|
||
|
// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented)
|
||
|
// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer
|
||
|
#if _MSVC_LANG >= 201402L
|
||
|
#define CLI11_CPP14
|
||
|
#if _MSVC_LANG > 201402L && _MSC_VER >= 1910
|
||
|
#define CLI11_CPP17
|
||
|
#if _MSVC_LANG > 201703L && _MSC_VER >= 1910
|
||
|
#define CLI11_CPP20
|
||
|
#endif
|
||
|
#endif
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#if defined(CLI11_CPP14)
|
||
|
#define CLI11_DEPRECATED(reason) [[deprecated(reason)]]
|
||
|
#elif defined(_MSC_VER)
|
||
|
#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason))
|
||
|
#else
|
||
|
#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason)))
|
||
|
#endif
|
||
|
|
||
|
// GCC < 10 doesn't ignore this in unevaluated contexts
|
||
|
#if !defined(CLI11_CPP17) || \
|
||
|
(defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 10 && __GNUC__ > 4)
|
||
|
#define CLI11_NODISCARD
|
||
|
#else
|
||
|
#define CLI11_NODISCARD [[nodiscard]]
|
||
|
#endif
|
||
|
|
||
|
/** detection of rtti */
|
||
|
#ifndef CLI11_USE_STATIC_RTTI
|
||
|
#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI)
|
||
|
#define CLI11_USE_STATIC_RTTI 1
|
||
|
#elif defined(__cpp_rtti)
|
||
|
#if(defined(_CPPRTTI) && _CPPRTTI == 0)
|
||
|
#define CLI11_USE_STATIC_RTTI 1
|
||
|
#else
|
||
|
#define CLI11_USE_STATIC_RTTI 0
|
||
|
#endif
|
||
|
#elif(defined(__GCC_RTTI) && __GXX_RTTI)
|
||
|
#define CLI11_USE_STATIC_RTTI 0
|
||
|
#else
|
||
|
#define CLI11_USE_STATIC_RTTI 1
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
/** Inline macro **/
|
||
|
#ifdef CLI11_COMPILE
|
||
|
#define CLI11_INLINE
|
||
|
#else
|
||
|
#define CLI11_INLINE inline
|
||
|
#endif
|
||
|
|
||
|
|
||
|
|
||
|
// C standard library
|
||
|
// Only needed for existence checking
|
||
|
#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM
|
||
|
#if __has_include(<filesystem>)
|
||
|
// Filesystem cannot be used if targeting macOS < 10.15
|
||
|
#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500
|
||
|
#define CLI11_HAS_FILESYSTEM 0
|
||
|
#elif defined(__wasi__)
|
||
|
// As of wasi-sdk-14, filesystem is not implemented
|
||
|
#define CLI11_HAS_FILESYSTEM 0
|
||
|
#else
|
||
|
#include <filesystem>
|
||
|
#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703
|
||
|
#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9
|
||
|
#define CLI11_HAS_FILESYSTEM 1
|
||
|
#elif defined(__GLIBCXX__)
|
||
|
// if we are using gcc and Version <9 default to no filesystem
|
||
|
#define CLI11_HAS_FILESYSTEM 0
|
||
|
#else
|
||
|
#define CLI11_HAS_FILESYSTEM 1
|
||
|
#endif
|
||
|
#else
|
||
|
#define CLI11_HAS_FILESYSTEM 0
|
||
|
#endif
|
||
|
#endif
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
|
||
|
#include <filesystem> // NOLINT(build/include)
|
||
|
#else
|
||
|
#include <sys/stat.h>
|
||
|
#include <sys/types.h>
|
||
|
#endif
|
||
|
|
||
|
|
||
|
|
||
|
namespace CLI {
|
||
|
|
||
|
|
||
|
/// Include the items in this namespace to get free conversion of enums to/from streams.
|
||
|
/// (This is available inside CLI as well, so CLI11 will use this without a using statement).
|
||
|
namespace enums {
|
||
|
|
||
|
/// output streaming for enumerations
|
||
|
template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
|
||
|
std::ostream &operator<<(std::ostream &in, const T &item) {
|
||
|
// make sure this is out of the detail namespace otherwise it won't be found when needed
|
||
|
return in << static_cast<typename std::underlying_type<T>::type>(item);
|
||
|
}
|
||
|
|
||
|
} // namespace enums
|
||
|
|
||
|
/// Export to CLI namespace
|
||
|
using enums::operator<<;
|
||
|
|
||
|
namespace detail {
|
||
|
/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not
|
||
|
/// produce overflow for some expected uses
|
||
|
constexpr int expected_max_vector_size{1 << 29};
|
||
|
// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
|
||
|
/// Split a string by a delim
|
||
|
CLI11_INLINE std::vector<std::string> split(const std::string &s, char delim);
|
||
|
|
||
|
/// Simple function to join a string
|
||
|
template <typename T> std::string join(const T &v, std::string delim = ",") {
|
||
|
std::ostringstream s;
|
||
|
auto beg = std::begin(v);
|
||
|
auto end = std::end(v);
|
||
|
if(beg != end)
|
||
|
s << *beg++;
|
||
|
while(beg != end) {
|
||
|
s << delim << *beg++;
|
||
|
}
|
||
|
return s.str();
|
||
|
}
|
||
|
|
||
|
/// Simple function to join a string from processed elements
|
||
|
template <typename T,
|
||
|
typename Callable,
|
||
|
typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type>
|
||
|
std::string join(const T &v, Callable func, std::string delim = ",") {
|
||
|
std::ostringstream s;
|
||
|
auto beg = std::begin(v);
|
||
|
auto end = std::end(v);
|
||
|
auto loc = s.tellp();
|
||
|
while(beg != end) {
|
||
|
auto nloc = s.tellp();
|
||
|
if(nloc > loc) {
|
||
|
s << delim;
|
||
|
loc = nloc;
|
||
|
}
|
||
|
s << func(*beg++);
|
||
|
}
|
||
|
return s.str();
|
||
|
}
|
||
|
|
||
|
/// Join a string in reverse order
|
||
|
template <typename T> std::string rjoin(const T &v, std::string delim = ",") {
|
||
|
std::ostringstream s;
|
||
|
for(std::size_t start = 0; start < v.size(); start++) {
|
||
|
if(start > 0)
|
||
|
s << delim;
|
||
|
s << v[v.size() - start - 1];
|
||
|
}
|
||
|
return s.str();
|
||
|
}
|
||
|
|
||
|
// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string
|
||
|
|
||
|
/// Trim whitespace from left of string
|
||
|
CLI11_INLINE std::string <rim(std::string &str);
|
||
|
|
||
|
/// Trim anything from left of string
|
||
|
CLI11_INLINE std::string <rim(std::string &str, const std::string &filter);
|
||
|
|
||
|
/// Trim whitespace from right of string
|
||
|
CLI11_INLINE std::string &rtrim(std::string &str);
|
||
|
|
||
|
/// Trim anything from right of string
|
||
|
CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter);
|
||
|
|
||
|
/// Trim whitespace from string
|
||
|
inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); }
|
||
|
|
||
|
/// Trim anything from string
|
||
|
inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); }
|
||
|
|
||
|
/// Make a copy of the string and then trim it
|
||
|
inline std::string trim_copy(const std::string &str) {
|
||
|
std::string s = str;
|
||
|
return trim(s);
|
||
|
}
|
||
|
|
||
|
/// remove quotes at the front and back of a string either '"' or '\''
|
||
|
CLI11_INLINE std::string &remove_quotes(std::string &str);
|
||
|
|
||
|
/// Add a leader to the beginning of all new lines (nothing is added
|
||
|
/// at the start of the first line). `"; "` would be for ini files
|
||
|
///
|
||
|
/// Can't use Regex, or this would be a subs.
|
||
|
CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input);
|
||
|
|
||
|
/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered)
|
||
|
inline std::string trim_copy(const std::string &str, const std::string &filter) {
|
||
|
std::string s = str;
|
||
|
return trim(s, filter);
|
||
|
}
|
||
|
/// Print a two part "help" string
|
||
|
CLI11_INLINE std::ostream &
|
||
|
format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid);
|
||
|
|
||
|
/// Print subcommand aliases
|
||
|
CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid);
|
||
|
|
||
|
/// Verify the first character of an option
|
||
|
/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with
|
||
|
template <typename T> bool valid_first_char(T c) { return ((c != '-') && (c != '!') && (c != ' ') && c != '\n'); }
|
||
|
|
||
|
/// Verify following characters of an option
|
||
|
template <typename T> bool valid_later_char(T c) {
|
||
|
// = and : are value separators, { has special meaning for option defaults,
|
||
|
// and \n would just be annoying to deal with in many places allowing space here has too much potential for
|
||
|
// inadvertent entry errors and bugs
|
||
|
return ((c != '=') && (c != ':') && (c != '{') && (c != ' ') && c != '\n');
|
||
|
}
|
||
|
|
||
|
/// Verify an option/subcommand name
|
||
|
CLI11_INLINE bool valid_name_string(const std::string &str);
|
||
|
|
||
|
/// Verify an app name
|
||
|
inline bool valid_alias_name_string(const std::string &str) {
|
||
|
static const std::string badChars(std::string("\n") + '\0');
|
||
|
return (str.find_first_of(badChars) == std::string::npos);
|
||
|
}
|
||
|
|
||
|
/// check if a string is a container segment separator (empty or "%%")
|
||
|
inline bool is_separator(const std::string &str) {
|
||
|
static const std::string sep("%%");
|
||
|
return (str.empty() || str == sep);
|
||
|
}
|
||
|
|
||
|
/// Verify that str consists of letters only
|
||
|
inline bool isalpha(const std::string &str) {
|
||
|
return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); });
|
||
|
}
|
||
|
|
||
|
/// Return a lower case version of a string
|
||
|
inline std::string to_lower(std::string str) {
|
||
|
std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) {
|
||
|
return std::tolower(x, std::locale());
|
||
|
});
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/// remove underscores from a string
|
||
|
inline std::string remove_underscore(std::string str) {
|
||
|
str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str));
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/// Find and replace a substring with another substring
|
||
|
CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to);
|
||
|
|
||
|
/// check if the flag definitions has possible false flags
|
||
|
inline bool has_default_flag_values(const std::string &flags) {
|
||
|
return (flags.find_first_of("{!") != std::string::npos);
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE void remove_default_flag_values(std::string &flags);
|
||
|
|
||
|
/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores
|
||
|
CLI11_INLINE std::ptrdiff_t find_member(std::string name,
|
||
|
const std::vector<std::string> names,
|
||
|
bool ignore_case = false,
|
||
|
bool ignore_underscore = false);
|
||
|
|
||
|
/// Find a trigger string and call a modify callable function that takes the current string and starting position of the
|
||
|
/// trigger and returns the position in the string to search for the next trigger string
|
||
|
template <typename Callable> inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) {
|
||
|
std::size_t start_pos = 0;
|
||
|
while((start_pos = str.find(trigger, start_pos)) != std::string::npos) {
|
||
|
start_pos = modify(str, start_pos);
|
||
|
}
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/// Split a string '"one two" "three"' into 'one two', 'three'
|
||
|
/// Quote characters can be ` ' or "
|
||
|
CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter = '\0');
|
||
|
|
||
|
/// This function detects an equal or colon followed by an escaped quote after an argument
|
||
|
/// then modifies the string to replace the equality with a space. This is needed
|
||
|
/// to allow the split up function to work properly and is intended to be used with the find_and_modify function
|
||
|
/// the return value is the offset+1 which is required by the find_and_modify function.
|
||
|
CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset);
|
||
|
|
||
|
/// Add quotes if the string contains spaces
|
||
|
CLI11_INLINE std::string &add_quotes_if_needed(std::string &str);
|
||
|
|
||
|
} // namespace detail
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
namespace detail {
|
||
|
CLI11_INLINE std::vector<std::string> split(const std::string &s, char delim) {
|
||
|
std::vector<std::string> elems;
|
||
|
// Check to see if empty string, give consistent result
|
||
|
if(s.empty()) {
|
||
|
elems.emplace_back();
|
||
|
} else {
|
||
|
std::stringstream ss;
|
||
|
ss.str(s);
|
||
|
std::string item;
|
||
|
while(std::getline(ss, item, delim)) {
|
||
|
elems.push_back(item);
|
||
|
}
|
||
|
}
|
||
|
return elems;
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE std::string <rim(std::string &str) {
|
||
|
auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
|
||
|
str.erase(str.begin(), it);
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE std::string <rim(std::string &str, const std::string &filter) {
|
||
|
auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
|
||
|
str.erase(str.begin(), it);
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE std::string &rtrim(std::string &str) {
|
||
|
auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
|
||
|
str.erase(it.base(), str.end());
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter) {
|
||
|
auto it =
|
||
|
std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
|
||
|
str.erase(it.base(), str.end());
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE std::string &remove_quotes(std::string &str) {
|
||
|
if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) {
|
||
|
if(str.front() == str.back()) {
|
||
|
str.pop_back();
|
||
|
str.erase(str.begin(), str.begin() + 1);
|
||
|
}
|
||
|
}
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input) {
|
||
|
std::string::size_type n = 0;
|
||
|
while(n != std::string::npos && n < input.size()) {
|
||
|
n = input.find('\n', n);
|
||
|
if(n != std::string::npos) {
|
||
|
input = input.substr(0, n + 1) + leader + input.substr(n + 1);
|
||
|
n += leader.size();
|
||
|
}
|
||
|
}
|
||
|
return input;
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE std::ostream &
|
||
|
format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) {
|
||
|
name = " " + name;
|
||
|
out << std::setw(static_cast<int>(wid)) << std::left << name;
|
||
|
if(!description.empty()) {
|
||
|
if(name.length() >= wid)
|
||
|
out << "\n" << std::setw(static_cast<int>(wid)) << "";
|
||
|
for(const char c : description) {
|
||
|
out.put(c);
|
||
|
if(c == '\n') {
|
||
|
out << std::setw(static_cast<int>(wid)) << "";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
out << "\n";
|
||
|
return out;
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid) {
|
||
|
if(!aliases.empty()) {
|
||
|
out << std::setw(static_cast<int>(wid)) << " aliases: ";
|
||
|
bool front = true;
|
||
|
for(const auto &alias : aliases) {
|
||
|
if(!front) {
|
||
|
out << ", ";
|
||
|
} else {
|
||
|
front = false;
|
||
|
}
|
||
|
out << detail::fix_newlines(" ", alias);
|
||
|
}
|
||
|
out << "\n";
|
||
|
}
|
||
|
return out;
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE bool valid_name_string(const std::string &str) {
|
||
|
if(str.empty() || !valid_first_char(str[0])) {
|
||
|
return false;
|
||
|
}
|
||
|
auto e = str.end();
|
||
|
for(auto c = str.begin() + 1; c != e; ++c)
|
||
|
if(!valid_later_char(*c))
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) {
|
||
|
|
||
|
std::size_t start_pos = 0;
|
||
|
|
||
|
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
||
|
str.replace(start_pos, from.length(), to);
|
||
|
start_pos += to.length();
|
||
|
}
|
||
|
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE void remove_default_flag_values(std::string &flags) {
|
||
|
auto loc = flags.find_first_of('{', 2);
|
||
|
while(loc != std::string::npos) {
|
||
|
auto finish = flags.find_first_of("},", loc + 1);
|
||
|
if((finish != std::string::npos) && (flags[finish] == '}')) {
|
||
|
flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc),
|
||
|
flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1);
|
||
|
}
|
||
|
loc = flags.find_first_of('{', loc + 1);
|
||
|
}
|
||
|
flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE std::ptrdiff_t
|
||
|
find_member(std::string name, const std::vector<std::string> names, bool ignore_case, bool ignore_underscore) {
|
||
|
auto it = std::end(names);
|
||
|
if(ignore_case) {
|
||
|
if(ignore_underscore) {
|
||
|
name = detail::to_lower(detail::remove_underscore(name));
|
||
|
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
|
||
|
return detail::to_lower(detail::remove_underscore(local_name)) == name;
|
||
|
});
|
||
|
} else {
|
||
|
name = detail::to_lower(name);
|
||
|
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
|
||
|
return detail::to_lower(local_name) == name;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
} else if(ignore_underscore) {
|
||
|
name = detail::remove_underscore(name);
|
||
|
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
|
||
|
return detail::remove_underscore(local_name) == name;
|
||
|
});
|
||
|
} else {
|
||
|
it = std::find(std::begin(names), std::end(names), name);
|
||
|
}
|
||
|
|
||
|
return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter) {
|
||
|
|
||
|
const std::string delims("\'\"`");
|
||
|
auto find_ws = [delimiter](char ch) {
|
||
|
return (delimiter == '\0') ? std::isspace<char>(ch, std::locale()) : (ch == delimiter);
|
||
|
};
|
||
|
trim(str);
|
||
|
|
||
|
std::vector<std::string> output;
|
||
|
bool embeddedQuote = false;
|
||
|
char keyChar = ' ';
|
||
|
while(!str.empty()) {
|
||
|
if(delims.find_first_of(str[0]) != std::string::npos) {
|
||
|
keyChar = str[0];
|
||
|
auto end = str.find_first_of(keyChar, 1);
|
||
|
while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes
|
||
|
end = str.find_first_of(keyChar, end + 1);
|
||
|
embeddedQuote = true;
|
||
|
}
|
||
|
if(end != std::string::npos) {
|
||
|
output.push_back(str.substr(1, end - 1));
|
||
|
if(end + 2 < str.size()) {
|
||
|
str = str.substr(end + 2);
|
||
|
} else {
|
||
|
str.clear();
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
output.push_back(str.substr(1));
|
||
|
str = "";
|
||
|
}
|
||
|
} else {
|
||
|
auto it = std::find_if(std::begin(str), std::end(str), find_ws);
|
||
|
if(it != std::end(str)) {
|
||
|
std::string value = std::string(str.begin(), it);
|
||
|
output.push_back(value);
|
||
|
str = std::string(it + 1, str.end());
|
||
|
} else {
|
||
|
output.push_back(str);
|
||
|
str = "";
|
||
|
}
|
||
|
}
|
||
|
// transform any embedded quotes into the regular character
|
||
|
if(embeddedQuote) {
|
||
|
output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar));
|
||
|
embeddedQuote = false;
|
||
|
}
|
||
|
trim(str);
|
||
|
}
|
||
|
return output;
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) {
|
||
|
auto next = str[offset + 1];
|
||
|
if((next == '\"') || (next == '\'') || (next == '`')) {
|
||
|
auto astart = str.find_last_of("-/ \"\'`", offset - 1);
|
||
|
if(astart != std::string::npos) {
|
||
|
if(str[astart] == ((str[offset] == '=') ? '-' : '/'))
|
||
|
str[offset] = ' '; // interpret this as a space so the split_up works properly
|
||
|
}
|
||
|
}
|
||
|
return offset + 1;
|
||
|
}
|
||
|
|
||
|
CLI11_INLINE std::string &add_quotes_if_needed(std::string &str) {
|
||
|
if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) {
|
||
|
char quote = str.find('"') < str.find('\'') ? '\'' : '"';
|
||
|
if(str.find(' ') != std::string::npos) {
|
||
|
str.insert(0, 1, quote);
|
||
|
str.append(1, quote);
|
||
|
}
|
||
|
}
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
} // namespace detail
|
||
|
|
||
|
|
||
|
|
||
|
// Use one of these on all error classes.
|
||
|
// These are temporary and are undef'd at the end of this file.
|
||
|
#define CLI11_ERROR_DEF(parent, name) \
|
||
|
protected: \
|
||
|
name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \
|
||
|
name(std::string ename, std::string msg, ExitCodes exit_code) \
|
||
|
: parent(std::move(ename), std::move(msg), exit_code) {} \
|
||
|
\
|
||
|
public: \
|
||
|
name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \
|
||
|
name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {}
|
||
|
|
||
|
// This is added after the one above if a class is used directly and builds its own message
|
||
|
#define CLI11_ERROR_SIMPLE(name) \
|
||
|
explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {}
|
||
|
|
||
|
/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut,
|
||
|
/// int values from e.get_error_code().
|
||
|
enum class ExitCodes {
|
||
|
Success = 0,
|
||
|
IncorrectConstruction = 100,
|
||
|
BadNameString,
|
||
|
OptionAlreadyAdded,
|
||
|
FileError,
|
||
|
ConversionError,
|
||
|
ValidationError,
|
||
|
RequiredError,
|
||
|
RequiresError,
|
||
|
ExcludesError,
|
||
|
ExtrasError,
|
||
|
ConfigError,
|
||
|
InvalidError,
|
||
|
HorribleError,
|
||
|
OptionNotFound,
|
||
|
ArgumentMismatch,
|
||
|
BaseClass = 127
|
||
|
};
|
||
|
|
||
|
// Error definitions
|
||
|
|
||
|
/// @defgroup error_group Errors
|
||
|
/// @brief Errors thrown by CLI11
|
||
|
///
|
||
|
/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors.
|
||
|
/// @{
|
||
|
|
||
|
/// All errors derive from this one
|
||
|
class Error : public std::runtime_error {
|
||
|
int actual_exit_code;
|
||
|
std::string error_name{"Error"};
|
||
|
|
||
|
public:
|
||
|
CLI11_NODISCARD int get_exit_code() const { return actual_exit_code; }
|
||
|
|
||
|
CLI11_NODISCARD std::string get_name() const { return error_name; }
|
||
|
|
||
|
Error(std::string name, std::string msg, int exit_code = static_cast<int>(ExitCodes::BaseClass))
|
||
|
: runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {}
|
||
|
|
||
|
Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast<int>(exit_code)) {}
|
||
|
};
|
||
|
|
||
|
// Note: Using Error::Error constructors does not work on GCC 4.7
|
||
|
|
||
|
/// Construction errors (not in parsing)
|
||
|
class ConstructionError : public Error {
|
||
|
CLI11_ERROR_DEF(Error, ConstructionError)
|
||
|
};
|
||
|
|
||
|
/// Thrown when an option is set to conflicting values (non-vector and multi args, for example)
|
||
|
class IncorrectConstruction : public ConstructionError {
|
||
|
CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction)
|
||
|
CLI11_ERROR_SIMPLE(IncorrectConstruction)
|
||
|
static IncorrectConstruction PositionalFlag(std::string name) {
|
||
|
return IncorrectConstruction(name + ": Flags cannot be positional");
|
||
|
}
|
||
|
static IncorrectConstruction Set0Opt(std::string name) {
|
||
|
return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead");
|
||
|
}
|
||
|
static IncorrectConstruction SetFlag(std::string name) {
|
||
|
return IncorrectConstruction(name + ": Cannot set an expected number for flags");
|
||
|
}
|
||
|
static IncorrectConstruction ChangeNotVector(std::string name) {
|
||
|
return IncorrectConstruction(name + ": You can only change the expected arguments for vectors");
|
||
|
}
|
||
|
static IncorrectConstruction AfterMultiOpt(std::string name) {
|
||
|
return IncorrectConstruction(
|
||
|
name + ": You can't change expected arguments after you've changed the multi option policy!");
|
||
|
}
|
||
|
static IncorrectConstruction MissingOption(std::string name) {
|
||
|
return IncorrectConstruction("Option " + name + " is not defined");
|
||
|
}
|
||
|
static IncorrectConstruction MultiOptionPolicy(std::string name) {
|
||
|
return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options");
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/// Thrown on construction of a bad name
|
||
|
class BadNameString : public ConstructionError {
|
||
|
CLI11_ERROR_DEF(ConstructionError, BadNameString)
|
||
|
CLI11_ERROR_SIMPLE(BadNameString)
|
||
|
static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); }
|
||
|
static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); }
|
||
|
static BadNameString DashesOnly(std::string name) {
|
||
|
return BadNameString("Must have a name, not just dashes: " + name);
|
||
|
}
|
||
|
static BadNameString MultiPositionalNames(std::string name) {
|
||
|
return BadNameString("Only one positional name allowed, remove: " + name);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/// Thrown when an option already exists
|
||
|
class OptionAlreadyAdded : public ConstructionError {
|
||
|
CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded)
|
||
|
explicit OptionAlreadyAdded(std::string name)
|
||
|
: OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {}
|
||
|
static OptionAlreadyAdded Requires(std::string name, std::string other) {
|
||
|
return {name + " requires " + other, ExitCodes::OptionAlreadyAdded};
|
||
|
}
|
||
|
static OptionAlreadyAdded Excludes(std::string name, std::string other) {
|
||
|
return {name + " excludes " + other, ExitCodes::OptionAlreadyAdded};
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Parsing errors
|
||
|
|
||
|
/// Anything that can error in Parse
|
||
|
class ParseError : public Error {
|
||
|
CLI11_ERROR_DEF(Error, ParseError)
|
||
|
};
|
||
|
|
||
|
// Not really "errors"
|
||
|
|
||
|
/// This is a successful completion on parsing, supposed to exit
|
||
|
class Success : public ParseError {
|
||
|
CLI11_ERROR_DEF(ParseError, Success)
|
||
|
Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {}
|
||
|
};
|
||
|
|
||
|
/// -h or --help on command line
|
||
|
class CallForHelp : public Success {
|
||
|
CLI11_ERROR_DEF(Success, CallForHelp)
|
||
|
CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
|
||
|
};
|
||
|
|
||
|
/// Usually something like --help-all on command line
|
||
|
class CallForAllHelp : public Success {
|
||
|
CLI11_ERROR_DEF(Success, CallForAllHelp)
|
||
|
CallForAllHelp()
|
||
|
: CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
|
||
|
};
|
||
|
|
||
|
/// -v or --version on command line
|
||
|
class CallForVersion : public Success {
|
||
|
CLI11_ERROR_DEF(Success, CallForVersion)
|
||
|
CallForVersion()
|
||
|
: CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {}
|
||
|
};
|
||
|
|
||
|
/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code.
|
||
|
class RuntimeError : public ParseError {
|
||
|
CLI11_ERROR_DEF(ParseError, RuntimeError)
|
||
|
explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {}
|
||
|
};
|
||
|
|
||
|
/// Thrown when parsing an INI file and it is missing
|
||
|
class FileError : public ParseError {
|
||
|
CLI11_ERROR_DEF(ParseError, FileError)
|
||
|
CLI11_ERROR_SIMPLE(FileError)
|
||
|
static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); }
|
||
|
};
|
||
|
|
||
|
/// Thrown when conversion call back fails, such as when an int fails to coerce to a string
|
||
|
class ConversionError : public ParseError {
|
||
|
CLI11_ERROR_DEF(ParseError, ConversionError)
|
||
|
CLI11_ERROR_SIMPLE(ConversionError)
|
||
|
ConversionError(std::string member, std::string name)
|
||
|
: ConversionError("The value " + member + " is not an allowed value for " + name) {}
|
||
|
ConversionError(std::string name, std::vector<std::string> results)
|
||
|
: ConversionError("Could not convert: " + name + " = " + detail::join(results)) {}
|
||
|
static ConversionError TooManyInputsFlag(std::string name) {
|
||
|
return ConversionError(name + ": too many inputs for a flag");
|
||
|
}
|
||
|
static ConversionError TrueFalse(std::string name) {
|
||
|
return ConversionError(name + ": Should be true/false or a number");
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/// Thrown when validation of results fails
|
||
|
class ValidationError : public ParseError {
|
||
|
CLI11_ERROR_DEF(ParseError, ValidationError)
|
||
|
CLI11_ERROR_SIMPLE(ValidationError)
|
||
|
explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {}
|
||
|
};
|
||
|
|
||
|
/// Thrown when a required option is missing
|
||
|
class RequiredError : public ParseError {
|
||
|
CLI11_ERROR_DEF(ParseError, RequiredError)
|
||
|
explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {}
|
||
|
static RequiredError Subcommand(std::size_t min_subcom) {
|
||
|
if(min_subcom == 1) {
|
||
|
return RequiredError("A subcommand");
|
||
|
}
|
||
|
return {"Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError};
|
||
|
}
|
||
|
static RequiredError
|
||
|
Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) {
|
||
|
if((min_option == 1) && (max_option == 1) && (used == 0))
|
||
|
return RequiredError("Exactly 1 option from [" + option_list + "]");
|
||
|
if((min_option == 1) && (max_option == 1) && (used > 1)) {
|
||
|
return {"Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) +
|
||
|
" were given",
|
||
|
ExitCodes::RequiredError};
|
||
|
}
|
||
|
if((min_option == 1) && (used == 0))
|
||
|
return RequiredError("At least 1 option from [" + option_list + "]");
|
||
|
if(used < min_option) {
|
||
|
return {"Requires at least " + std::to_string(min_option) + " options used and only " +
|
||
|
std::to_string(used) + "were given from [" + option_list + "]",
|
||
|
ExitCodes::RequiredError};
|
||
|
}
|
||
|
if(max_option == 1)
|
||
|
return {"Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError};
|
||
|
|
||
|
return {"Requires at most " + std::to_string(max_option) + " options be used and " + std::to_string(used) +
|
||
|
"were given from [" + option_list + "]",
|
||
|
ExitCodes::RequiredError};
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/// Thrown when the wrong number of arguments has been received
|
||
|
class ArgumentMismatch : public ParseError {
|
||
|
CLI11_ERROR_DEF(ParseError, ArgumentMismatch)
|
||
|
CLI11_ERROR_SIMPLE(ArgumentMismatch)
|
||
|
ArgumentMismatch(std::string name, int expected, std::size_t received)
|
||
|
: ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name +
|
||
|
", got " + std::to_string(received))
|
||
|
: ("Expected at least " + std::to_string(-expected) + " arguments to " + name +
|
||
|
", got " + std::to_string(received)),
|
||
|
ExitCodes::ArgumentMismatch) {}
|
||
|
|
||
|
static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) {
|
||
|
return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " +
|
||
|
std::to_string(received));
|
||
|
}
|
||
|
static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) {
|
||
|
return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " +
|
||
|
std::to_string(received));
|
||
|
}
|
||
|
static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
|
||
|
return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing");
|
||
|
}
|
||
|
static ArgumentMismatch FlagOverride(std::string name) {
|
||
|
return ArgumentMismatch(name + " was given a disallowed flag override");
|
||
|
}
|
||
|
static ArgumentMismatch PartialType(std::string name, int num, std::string type) {
|
||
|
return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) +
|
||
|
" required for each element");
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/// Thrown when a requires option is missing
|
||
|
class RequiresError : public ParseError {
|
||
|
CLI11_ERROR_DEF(ParseError, RequiresError)
|
||
|
RequiresError(std::string curname, std::string subname)
|
||
|
: RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {}
|
||
|
};
|
||
|
|
||
|
/// Thrown when an excludes option is present
|
||
|
class ExcludesError : public ParseError {
|
||
|
CLI11_ERROR_DEF(ParseError, ExcludesError)
|
||
|
ExcludesError(std::string curname, std::string subname)
|
||
|
: ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {}
|
||
|
};
|
||
|
|
||
|
/// Thrown when too many positionals or options are found
|
||
|
class ExtrasError : public ParseError {
|
||
|
CLI11_ERROR_DEF(ParseError, ExtrasError)
|
||
|
explicit ExtrasError(std::vector<std::string> args)
|
||
|
: ExtrasError((args.size() > 1 ? "The following arguments were not expected: "
|
||
|
: "The following argument was not expected: ") +
|
||
|
detail::rjoin(args, " "),
|
||
|
ExitCodes::ExtrasError) {}
|
||
|
ExtrasError(const std::string &name, std::vector<std::string> args)
|
||
|
: ExtrasError(name,
|
||
|
(args.size() > 1 ? "The following arguments were not expected: "
|
||
|
: "The following argument was not expected: ") +
|
||
|
detail::rjoin(args, " "),
|
||
|
ExitCodes::ExtrasError) {}
|
||
|
};
|
||
|
|
||
|
/// Thrown when extra values are found in an INI file
|
||
|
class ConfigError : public ParseError {
|
||
|
CLI11_ERROR_DEF(ParseError, ConfigError)
|
||
|
CLI11_ERROR_SIMPLE(ConfigError)
|
||
|
static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); }
|
||
|
static ConfigError NotConfigurable(std::string item) {
|
||
|
return ConfigError(item + ": This option is not allowed in a configuration file");
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/// Thrown when validation fails before parsing
|
||
|
class InvalidError : public ParseError {
|
||
|
CLI11_ERROR_DEF(ParseError, InvalidError)
|
||
|
explicit InvalidError(std::string name)
|
||
|
: InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) {
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/// This is just a safety check to verify selection and parsing match - you should not ever see it
|
||
|
/// Strings are directly added to this error, but again, it should never be seen.
|
||
|
class HorribleError : public ParseError {
|
||
|
CLI11_ERROR_DEF(ParseError, HorribleError)
|
||
|
CLI11_ERROR_SIMPLE(HorribleError)
|
||
|
};
|
||
|
|
||
|
// After parsing
|
||
|
|
||
|
/// Thrown when counting a non-existent option
|
||
|
class OptionNotFound : public Error {
|
||
|
CLI11_ERROR_DEF(Error, OptionNotFound)
|
||
|
explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {}
|
||
|
};
|
||
|
|
||
|
#undef CLI11_ERROR_DEF
|
||
|
#undef CLI11_ERROR_SIMPLE
|
||
|
|
||
|
/// @}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// Type tools
|
||
|
|
||
|
// Utilities for type enabling
|
||
|
namespace detail {
|
||
|
// Based generally on https://rmf.io/cxx11/almost-static-if
|
||
|
/// Simple empty scoped class
|
||
|
enum class enabler {};
|
||
|
|
||
|
/// An instance to use in EnableIf
|
||
|
constexpr enabler dummy = {};
|
||
|
} // namespace detail
|
||
|
|
||
|
/// A copy of enable_if_t from C++14, compatible with C++11.
|
||
|
///
|
||
|
/// We could check to see if C++14 is being used, but it does not hurt to redefine this
|
||
|
/// (even Google does this: https://github.com/google/skia/blob/main/include/private/SkTLogic.h)
|
||
|
/// It is not in the std namespace anyway, so no harm done.
|
||
|
template <bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type;
|
||
|
|
||
|
/// A copy of std::void_t from C++17 (helper for C++11 and C++14)
|
||
|
template <typename... Ts> struct make_void {
|
||
|
using type = void;
|
||
|
};
|
||
|
|
||
|
/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine
|
||
|
template <typename... Ts> using void_t = typename make_void<Ts...>::type;
|
||
|
|
||
|
/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine
|
||
|
template <bool B, class T, class F> using conditional_t = typename std::conditional<B, T, F>::type;
|
||
|
|
||
|
/// Check to see if something is bool (fail check by default)
|
||
|
template <typename T> struct is_bool : std::false_type {};
|
||
|
|
||
|
/// Check to see if something is bool (true if actually a bool)
|
||
|
template <> struct is_bool<bool> : std::true_type {};
|
||
|
|
||
|
/// Check to see if something is a shared pointer
|
||
|
template <typename T> struct is_shared_ptr : std::false_type {};
|
||
|
|
||
|
/// Check to see if something is a shared pointer (True if really a shared pointer)
|
||
|
template <typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
|
||
|
|
||
|
/// Check to see if something is a shared pointer (True if really a shared pointer)
|
||
|
template <typename T> struct is_shared_ptr<const std::shared_ptr<T>> : std::true_type {};
|
||
|
|
||
|
/// Check to see if something is copyable pointer
|
||
|
template <typename T> struct is_copyable_ptr {
|
||
|
static bool const value = is_shared_ptr<T>::value || std::is_pointer<T>::value;
|
||
|
};
|
||
|
|
||
|
/// This can be specialized to override the type deduction for IsMember.
|
||
|
template <typename T> struct IsMemberType {
|
||
|
using type = T;
|
||
|
};
|
||
|
|
||
|
/// The main custom type needed here is const char * should be a string.
|
||
|
template <> struct IsMemberType<const char *> {
|
||
|
using type = std::string;
|
||
|
};
|
||
|
|
||
|
namespace detail {
|
||
|
|
||
|
// These are utilities for IsMember and other transforming objects
|
||
|
|
||
|
/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that
|
||
|
/// pointer_traits<T> be valid.
|
||
|
|
||
|
/// not a pointer
|
||
|
template <typename T, typename Enable = void> struct element_type {
|
||
|
using type = T;
|
||
|
};
|
||
|
|
||
|
template <typename T> struct element_type<T, typename std::enable_if<is_copyable_ptr<T>::value>::type> {
|
||
|
using type = typename std::pointer_traits<T>::element_type;
|
||
|
};
|
||
|
|
||
|
/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of
|
||
|
/// the container
|
||
|
template <typename T> struct element_value_type {
|
||
|
using type = typename element_type<T>::type::value_type;
|
||
|
};
|
||
|
|
||
|
/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing.
|
||
|
template <typename T, typename _ = void> struct pair_adaptor : std::false_type {
|
||
|
using value_type = typename T::value_type;
|
||
|
using first_type = typename std::remove_const<value_type>::type;
|
||
|
using second_type = typename std::remove_const<value_type>::type;
|
||
|
|
||
|
/// Get the first value (really just the underlying value)
|
||
|
template <typename Q> static auto first(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) {
|
||
|
return std::forward<Q>(pair_value);
|
||
|
}
|
||
|
/// Get the second value (really just the underlying value)
|
||
|
template <typename Q> static auto second(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) {
|
||
|
return std::forward<Q>(pair_value);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/// Adaptor for map-like structure (true version, must have key_type and mapped_type).
|
||
|
/// This wraps a mapped container in a few utilities access it in a general way.
|
||
|
template <typename T>
|
||
|
struct pair_adaptor<
|
||
|
T,
|
||
|
conditional_t<false, void_t<typename T::value_type::first_type, typename T::value_type::second_type>, void>>
|
||
|
: std::true_type {
|
||
|
using value_type = typename T::value_type;
|
||
|
using first_type = typename std::remove_const<typename value_type::first_type>::type;
|
||
|
using second_type = typename std::remove_const<typename value_type::second_type>::type;
|
||
|
|
||
|
/// Get the first value (really just the underlying value)
|
||
|
template <typename Q> static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward<Q>(pair_value))) {
|
||
|
return std::get<0>(std::forward<Q>(pair_value));
|
||
|
}
|
||
|
/// Get the second value (really just the underlying value)
|
||
|
template <typename Q> static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward<Q>(pair_value))) {
|
||
|
return std::get<1>(std::forward<Q>(pair_value));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning
|
||
|
// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in
|
||
|
// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a
|
||
|
// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out.
|
||
|
// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be
|
||
|
// suppressed
|
||
|
#ifdef __GNUC__
|
||
|
#pragma GCC diagnostic push
|
||
|
#pragma GCC diagnostic ignored "-Wnarrowing"
|
||
|
#endif
|
||
|
// check for constructibility from a specific type and copy assignable used in the parse detection
|
||
|
template <typename T, typename C> class is_direct_constructible {
|
||
|
template <typename TT, typename CC>
|
||
|
static auto test(int, std::true_type) -> decltype(
|
||
|
// NVCC warns about narrowing conversions here
|
||
|
#ifdef __CUDACC__
|
||
|
#pragma diag_suppress 2361
|
||
|
#endif
|
||
|
TT{std::declval<CC>()}
|
||
|
#ifdef __CUDACC__
|
||
|
#pragma diag_default 2361
|
||
|
#endif
|
||
|
,
|
||
|
std::is_move_assignable<TT>());
|
||
|
|
||
|
template <typename TT, typename CC> static auto test(int, std::false_type) -> std::false_type;
|
||
|
|
||
|
template <typename, typename> static auto test(...) -> std::false_type;
|
||
|
|
||
|
public:
|
||
|
static constexpr bool value = decltype(test<T, C>(0, typename std::is_constructible<T, C>::type()))::value;
|
||
|
};
|
||
|
#ifdef __GNUC__
|
||
|
#pragma GCC diagnostic pop
|
||
|
#endif
|
||
|
|
||
|
// Check for output streamability
|
||
|
// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream
|
||
|
|
||
|
template <typename T, typename S = std::ostringstream> class is_ostreamable {
|
||
|
template <typename TT, typename SS>
|
||
|
static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type());
|
||
|
|
||
|
template <typename, typename> static auto test(...) -> std::false_type;
|
||
|
|
||
|
public:
|
||
|
static constexpr bool value = decltype(test<T, S>(0))::value;
|
||
|
};
|
||
|
|
||
|
/// Check for input streamability
|
||
|
template <typename T, typename S = std::istringstream> class is_istreamable {
|
||
|
template <typename TT, typename SS>
|
||
|
static auto test(int) -> decltype(std::declval<SS &>() >> std::declval<TT &>(), std::true_type());
|
||
|
|
||
|
template <typename, typename> static auto test(...) -> std::false_type;
|
||
|
|
||
|
public:
|
||
|
static constexpr bool value = decltype(test<T, S>(0))::value;
|
||
|
};
|
||
|
|
||
|
/// Check for complex
|
||
|
template <typename T> class is_complex {
|
||
|
template <typename TT>
|
||
|
static auto test(int) -> decltype(std::declval<TT>().real(), std::declval<TT>().imag(), std::true_type());
|
||
|
|
||
|
template <typename> static auto test(...) -> std::false_type;
|
||
|
|
||
|
public:
|
||
|
static constexpr bool value = decltype(test<T>(0))::value;
|
||
|
};
|
||
|
|
||
|
/// Templated operation to get a value from a stream
|
||
|
template <typename T, enable_if_t<is_istreamable<T>::value, detail::enabler> = detail::dummy>
|
||
|
bool from_stream(const std::string &istring, T &obj) {
|
||
|
std::istringstream is;
|
||
|
is.str(istring);
|
||
|
is >> obj;
|
||
|
return !is.fail() && !is.rdbuf()->in_avail();
|
||
|
}
|
||
|
|
||
|
template <typename T, enable_if_t<!is_istreamable<T>::value, detail::enabler> = detail::dummy>
|
||
|
bool from_stream(const std::string & /*istring*/, T & /*obj*/) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// check to see if an object is a mutable container (fail by default)
|
||
|
template <typename T, typename _ = void> struct is_mutable_container : std::false_type {};
|
||
|
|
||
|
/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and
|
||
|
/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed
|
||
|
/// from a std::string
|
||
|
template <typename T>
|
||
|
struct is_mutable_container<
|
||
|
T,
|
||
|
conditional_t<false,
|
||
|
void_t<typename T::value_type,
|
||
|
decltype(std::declval<T>().end()),
|
||
|
decltype(std::declval<T>().clear()),
|
||
|
decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(),
|
||
|
std::declval<const typename T::value_type &>()))>,
|
||
|
void>>
|
||
|
: public conditional_t<std::is_constructible<T, std::string>::value, std::false_type, std::true_type> {};
|
||
|
|
||
|
// check to see if an object is a mutable container (fail by default)
|
||
|
template <typename T, typename _ = void> struct is_readable_container : std::false_type {};
|
||
|
|
||
|
/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an end
|
||
|
/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from
|
||
|
/// a std::string
|
||
|
template <typename T>
|
||
|
struct is_readable_container<
|
||
|
T,
|
||
|
conditional_t<false, void_t<decltype(std::declval<T>().end()), decltype(std::declval<T>().begin())>, void>>
|
||
|
: public std::true_type {};
|
||
|
|
||
|
// check to see if an object is a wrapper (fail by default)
|
||
|
template <typename T, typename _ = void> struct is_wrapper : std::false_type {};
|
||
|
|
||
|
// check if an object is a wrapper (it has a value_type defined)
|
||
|
template <typename T>
|
||
|
struct is_wrapper<T, conditional_t<false, void_t<typename T::value_type>, void>> : public std::true_type {};
|
||
|
|
||
|
// Check for tuple like types, as in classes with a tuple_size type trait
|
||
|
template <typename S> class is_tuple_like {
|
||
|
template <typename SS>
|
||
|
// static auto test(int)
|
||
|
// -> decltype(std::conditional<(std::tuple_size<SS>::value > 0), std::true_type, std::false_type>::type());
|
||
|
static auto test(int) -> decltype(std::tuple_size<typename std::decay<SS>::type>::value, std::true_type{});
|
||
|
template <typename> static auto test(...) -> std::false_type;
|
||
|
|
||
|
public:
|
||
|
static constexpr bool value = decltype(test<S>(0))::value;
|
||
|
};
|
||
|
|
||
|
/// Convert an object to a string (directly forward if this can become a string)
|
||
|
template <typename T, enable_if_t<std::is_convertible<T, std::string>::value, detail::enabler> = detail::dummy>
|
||
|
auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
|
||
|
return std::forward<T>(value);
|
||
|
}
|
||
|
|
||
|
/// Construct a string from the object
|
||
|
template <typename T,
|
||
|
enable_if_t<std::is_constructible<std::string, T>::value && !std::is_convertible<T, std::string>::value,
|
||
|
detail::enabler> = detail::dummy>
|
||
|
std::string to_string(const T &value) {
|
||
|
return std::string(value); // NOLINT(google-readability-casting)
|
||
|
}
|
||
|
|
||
|
/// Convert an object to a string (streaming must be supported for that type)
|
||
|
template <typename T,
|
||
|
enable_if_t<!std::is_convertible<std::string, T>::value && !std::is_constructible<std::string, T>::value &&
|
||
|
is_ostreamable<T>::value,
|
||
|
detail::enabler> = detail::dummy>
|
||
|
std::string to_string(T &&value) {
|
||
|
std::stringstream stream;
|
||
|
stream << value;
|
||
|
return stream.str();
|
||
|
}
|
||
|
|
||
|
/// If conversion is not supported, return an empty string (streaming is not supported for that type)
|
||
|
template <typename T,
|
||
|
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
|
||
|
!is_readable_container<typename std::remove_const<T>::type>::value,
|
||
|
detail::enabler> = detail::dummy>
|
||
|
std::string to_string(T &&) {
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
/// convert a readable container to a string
|
||
|
template <typename T,
|
||
|
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
|
||
|
is_readable_container<T>::value,
|
||
|
detail::enabler> = detail::dummy>
|
||
|
std::string to_string(T &&variable) {
|
||
|
auto cval = variable.begin();
|
||
|
auto end = variable.end();
|
||
|
if(cval == end) {
|
||
|
return {"{}"};
|
||
|
}
|
||
|
std::vector<std::string> defaults;
|
||
|
while(cval != end) {
|
||
|
defaults.emplace_back(CLI::detail::to_string(*cval));
|
||
|
++cval;
|
||
|
}
|
||
|
return {"[" + detail::join(defaults) + "]"};
|
||
|
}
|
||
|
|
||
|
/// special template overload
|
||
|
template <typename T1,
|
||
|
typename T2,
|
||
|
typename T,
|
||
|
enable_if_t<std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
|
||
|
auto checked_to_string(T &&value) -> decltype(to_string(std::forward<T>(value))) {
|
||
|
return to_string(std::forward<T>(value));
|
||
|
}
|
||
|
|
||
|
/// special template overload
|
||
|
template <typename T1,
|
||
|
typename T2,
|
||
|
typename T,
|
||
|
enable_if_t<!std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
|
||
|
std::string checked_to_string(T &&) {
|
||
|
return std::string{};
|
||
|
}
|
||
|
/// get a string as a convertible value for arithmetic types
|
||
|
template <typename T, enable_if_t<std::is_arithmetic<T>::value, detail::enabler> = detail::dummy>
|
||
|
std::string value_string(const T &value) {
|
||
|
return std::to_string(value);
|
||
|
}
|
||
|
/// get a string as a convertible value for enumerations
|
||
|
template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy>
|
||
|
std::string value_string(const T &value) {
|
||
|
return std::to_string(static_cast<typename std::underlying_type<T>::type>(value));
|
||
|
}
|
||
|
/// for other types just use the regular to_string function
|
||
|
template <typename T,
|
||
|
enable_if_t<!std::is_enum<T>::value && !std::is_arithmetic<T>::value, detail::enabler> = detail::dummy>
|
||
|
auto value_string(const T &value) -> decltype(to_string(value)) {
|
||
|
return to_string(value);
|
||
|
}
|
||
|
|
||
|
/// template to get the underlying value type if it exists or use a default
|
||
|
template <typename T, typename def, typename Enable = void> struct wrapped_type {
|
||
|
using type = def;
|
||
|
};
|
||
|
|
||
|
/// Type size for regular object types that do not look like a tuple
|
||
|
template <typename T, typename def> struct wrapped_type<T, def, typename std::enable_if<is_wrapper<T>::value>::type> {
|
||
|
using type = typename T::value_type;
|
||
|
};
|
||
|
|
||
|
/// This will only trigger for actual void type
|
||
|
template <typename T, typename Enable = void> struct type_count_base {
|
||
|
static const int value{0};
|
||
|
};
|
||
|
|
||
|
/// Type size for regular object types that do not look like a tuple
|
||
|
template <typename T>
|
||
|
struct type_count_base<T,
|
||
|
typename std::enable_if<!is_tuple_like<T>::value && !is_mutable_container<T>::value &&
|
||
|
!std::is_void<T>::value>::type> {
|
||
|
static constexpr int value{1};
|
||
|
};
|
||
|
|
||
|
/// the base tuple size
|
||
|
template <typename T>
|
||
|
struct type_count_base<T, typename std::enable_if<is_tuple_like<T>::value && !is_mutable_container<T>::value>::type> {
|
||
|
static constexpr int value{std::tuple_size<T>::value};
|
||
|
};
|
||
|
|
||
|
/// Type count base for containers is the type_count_base of the individual element
|
||
|
template <typename T> struct type_count_base<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
|
||
|
static constexpr int value{type_count_base<typename T::value_type>::value};
|
||
|
};
|
||
|
|
||
|
/// Set of overloads to get the type size of an object
|
||
|
|
||
|
/// forward declare the subtype_count structure
|
||
|
template <typename T> struct subtype_count;
|
||
|
|
||
|
/// forward declare the subtype_count_min structure
|
||
|
template <typename T> struct subtype_count_min;
|
||
|
|
||
|
/// This will only trigger for actual void type
|
||
|
template <typename T, typename Enable = void> struct type_count {
|
||
|
static const int value{0};
|
||
|
};
|
||
|
|
||
|
/// Type size for regular object types that do not look like a tuple
|
||
|
template <typename T>
|
||
|
struct type_count<T,
|
||
|
typename std::enable_if<!is_wrapper<T>::value && !is_tuple_like<T>::value && !is_complex<T>::value &&
|
||
|
!std::is_void<T>::value>::type> {
|
||
|
static constexpr int value{1};
|
||
|
};
|
||
|
|
||
|
/// Type size for complex since it sometimes looks like a wrapper
|
||
|
template <typename T> struct type_count<T, typename std::enable_if<is_complex<T>::value>::type> {
|
||
|
static constexpr int value{2};
|
||
|
};
|
||
|
|
||
|
/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes)
|
||
|
template <typename T> struct type_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
|
||
|
static constexpr int value{subtype_count<typename T::value_type>::value};
|
||
|
};
|
||
|
|
||
|
/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes)
|
||
|
template <typename T>
|
||
|
struct type_count<T,
|
||
|
typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value &&
|
||
|
!is_mutable_container<T>::value>::type> {
|
||
|
static constexpr int value{type_count<typename T::value_type>::value};
|
||
|
};
|
||
|
|
||
|
/// 0 if the index > tuple size
|
||
|
template <typename T, std::size_t I>
|
||
|
constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size() {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/// Recursively generate the tuple type name
|
||
|
template <typename T, std::size_t I>
|
||
|
constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size() {
|
||
|
return subtype_count<typename std::tuple_element<I, T>::type>::value + tuple_type_size<T, I + 1>();
|
||
|
}
|
||
|
|
||
|
/// Get the type size of the sum of type sizes for all the individual tuple types
|
||
|
template <typename T> struct type_count<T, typename std::enable_if<is_tuple_like<T>::value>::type> {
|
||
|
static constexpr int value{tuple_type_size<T, 0>()};
|
||
|
};
|
||
|
|
||
|
/// definition of subtype count
|
||
|
template <typename T> struct subtype_count {
|
||
|
static constexpr int value{is_mutable_container<T>::value ? expected_max_vector_size : type_count<T>::value};
|
||
|
};
|
||
|
|
||
|
/// This will only trigger for actual void type
|
||
|
template <typename T, typename Enable = void> struct type_count_min {
|
||
|
static const int value{0};
|
||
|
};
|
||
|
|
||
|
/// Type size for regular object types that do not look like a tuple
|
||
|
template <typename T>
|
||
|
struct type_count_min<
|
||
|
T,
|
||
|
typename std::enable_if<!is_mutable_container<T>::value && !is_tuple_like<T>::value && !is_wrapper<T>::value &&
|
||
|
!is_complex<T>::value && !std::is_void<T>::value>::type> {
|
||
|
static constexpr int value{type_count<T>::value};
|
||
|
};
|
||
|
|
||
|
/// Type size for complex since it sometimes looks like a wrapper
|
||
|
template <typename T> struct type_count_min<T, typename std::enable_if<is_complex<T>::value>::type> {
|
||
|
static constexpr int value{1};
|
||
|
};
|
||
|
|
||
|
/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes)
|
||
|
template <typename T>
|
||
|
struct type_count_min<
|
||
|
T,
|
||
|
typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value>::type> {
|
||
|
static constexpr int value{subtype_count_min<typename T::value_type>::value};
|
||
|
};
|
||
|
|
||
|
/// 0 if the index > tuple size
|
||
|
template <typename T, std::size_t I>
|
||
|
constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size_min() {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/// Recursively generate the tuple type name
|
||
|
template <typename T, std::size_t I>
|
||
|
constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size_min() {
|
||
|
return subtype_count_min<typename std::tuple_element<I, T>::type>::value + tuple_type_size_min<T, I + 1>();
|
||
|
}
|
||
|
|
||
|
/// Get the type size of the sum of type sizes for all the individual tuple types
|
||
|
template <typename T> struct type_count_min<T, typename std::enable_if<is_tuple_like<T>::value>::type> {
|
||
|
static constexpr int value{tuple_type_size_min<T, 0>()};
|
||
|
};
|
||
|
|
||
|
/// definition of subtype count
|
||
|
template <typename T> struct subtype_count_min {
|
||
|
static constexpr int value{is_mutable_container<T>::value
|
||
|
? ((type_count<T>::value < expected_max_vector_size) ? type_count<T>::value : 0)
|
||
|
: type_count_min<T>::value};
|
||
|
};
|
||
|
|
||
|
/// This will only trigger for actual void type
|
||
|
template <typename T, typename Enable = void> struct expected_count {
|
||
|
static const int value{0};
|
||
|
};
|
||
|
|
||
|
/// For most types the number of expected items is 1
|
||
|
template <typename T>
|
||
|
struct expected_count<T,
|
||
|
typename std::enable_if<!is_mutable_container<T>::value && !is_wrapper<T>::value &&
|
||
|
!std::is_void<T>::value>::type> {
|
||
|
static constexpr int value{1};
|
||
|
};
|
||
|
/// number of expected items in a vector
|
||
|
template <typename T> struct expected_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
|
||
|
static constexpr int value{expected_max_vector_size};
|
||
|
};
|
||
First mostly functional version; let's call it v0.1.0.