A header-only C++17/20 library that enables Python-like **kwargs style argument passing without any macros or boilerplate, with automatic type conversion support.
About **kwargs in Python
In Python, **kwargs allows a function to accept any number of keyword arguments. It collects all Key=Value pairs into a dictionary, enabling flexible and scalable function definitions. Official Documentation.
Function Definition
-
In Python:
# Arbitrary keys def func(**kwargs): ... # Specific keys with defaults def func(*, name='empty_name', old=0): ...
-
In C++:
// Arbitrary keys auto func(Kwargs<> kwargs = {}); // Specific keys (defaults optional) auto func(Kwargs<"name"_opt, "old"_opt> kwargs = {});
External Call
-
In Python:
# Regular func(name='huanhuanonly', old=18) # Unexpected type func(name='huanhuanonly', old='18') # Reverse order func(old=18, name='huanhuanonly') # Empty parameters func()
-
In C++ (version 1):
// Regular func({ "name"_opt="huanhuanonly", "old"_opt=18 }); // Unexpected type func({ "name"_opt="huanhuanonly", "old"_opt="18" }); // Reverse order func({ "old"_opt=18, "name"_opt="huanhuanonly" }); // Empty parameters func();
-
In C++ (version 2):
// Regular func({ {"name", "huanhuanonly"}, {"old", 18} }); // Unexpected type func({ {"name", "huanhuanonly"}, {"old", "18"} }); // Reverse order func({ {"old", 18}, {"name", "huanhuanonly"} }); // Empty parameters func();
Accessing Values Internally
-
In Python:
str(kwargs['name']) if 'name' in kwargs else 'empty_name' int(kwargs['old']) if 'old' in kwargs else 0
-
In C++:
kwargs["name"].valueOr<std::string>("empty_name"); kwargs["old"].valueOr<int>(0); // 'kwargs["name"].hasValue()' // is equivalent to // 'if "name" in kwargs'
- "lastname" and "surname" as aliases for "name"
auto func(Kwargs<"name"_opt, "lastname"_opt, "surname"_opt> kwargs = {}) { // Use `or` to connect, the first name must be suffixed with `_opt` kwargs["name"_opt or "lastname" or "surname"].valueOr<std::string>(); }
-
std::vector<int>$\longrightarrow$ std::set<int>auto func(Kwargs<"numbers"_opt> kwargs = {}) { std::cout << kwargs["numbers"].valueOr<std::set<int>>() << std::endl; } func({ "number"_opt = std::vector<int>{1, 3, 5, 2, 3} }); // Output: [1, 2, 3, 5]
-
std::list<std::string_view>$\longrightarrow$ std::set<double>auto func(Kwargs<"numbers"_opt> kwargs = {}) { std::cout << kwargs["numbers"].valueOr<std::set<double>>() << std::endl; } func({ "number"_opt = std::list<std::string_view>{"1", "inf", "infinity", "3.14", "1e3", "1e-2"} }); // Output: [0.01, 1, 3.14, 1000, inf]
auto func(Kwargs<"size"_opt> kwargs = {})
{
std::cout << kwargs["size"]->typeName() << std::endl;
}
func({ "size"_opt = 520 }); // Output: int
func({ "size"_opt = 520f }); // Output: float
func({ "size"_opt = "hi" }); // Output: const char*Tip
Results may vary between compilers.
auto func(Kwargs<"data"_opt> kwargs = {})
{
if (kwargs["data"]->isSameType<int>())
{
std::cout << "int-data " << kwargs["data"].valueOr<int>() << std::endl;
}
else if (kwargs["data"]->isSameType<float>())
{
std::cout << "float-data " << kwargs["data"].valueOr<float>() << std::endl;
}
}
func({ "data"_opt = 996 }); // Output: int-data 996
func({ "data"_opt = 3.14f }); // Output: float-data 3.14struct Font
{
std::string faceName;
int size;
float escapement;
bool italic;
// Or Kwargs<> kwargs = {} without checking.
Font(Kwargs<
"faceName"_opt, /* Or */ "name"_opt,
"size"_opt,
"escapement"_opt,
"italic"_opt, /* Or */ "i"_opt> kwargs = {})
: faceName(kwargs["faceName"_opt or "name"].valueOr<std::string>())
, size(kwargs["size"].valueOr<int>(9))
, escapement(kwargs["escapement"].valueOr<float>(0.00f))
, italic(kwargs["italic"_opt or "i"].valueOr<bool>(false))
{ }
};Tip
name is an alias for faceName, and i for italic.
The following usages are valid:
-
Font()- Construct:
Font{ std::string(), 9, 0.00f, false }
- Construct:
-
Font({ })- Construct:
Font{ std::string(), 9, 0.00f, false }
- Construct:
-
Font({ {"name", "Arial"}, {"italic", true} })- Construct:
Font{ std::string("Arial"), 9, 0.00f, true }
- Construct:
-
Font({ {"italic", "true"}, {"name", "Arial"} })- Construct:
Font{ std::string("Arial"), 9, 0.00f, true }
- Construct:
-
Font({ "i"_opt="True", "faceName"_opt="Arial" })- Construct:
Font{ std::string("Arial"), 9, 0.00f, true }
- Construct:
-
Font({ {"size", 18}, {"escapement", 45} })- Construct:
Font{ std::string(), 18, 45.00f, false }
- Construct:
-
Font({ "size"_opt="18", "escapement"_opt="49.2" })- Construct:
Font{ std::string(), 18, 49.20f, false }
- Construct:
-
Font({ "size"_opt=18.8, {"escapement", 49.2}, "i"_opt='t' })- Construct:
Font{ std::string(), 18, 49.20f, true }
- Construct:
-
Supported by both Python (**kwargs) and cpp-kwargs:
- Arbitrary key order
- Missing or extra keys
- Specific key names
- Arbitrary value types
- Type information preservation
-
Additionally supported by cpp-kwargs:
- Automatic type conversion
- Low overhead, with
constexprresults at compile time (when applicable) - Case-insensitive keys (optional)
- Key aliases
Tip
C++20 is recommended for better constexpr support.
-
Only supported by Python:
- Dynamic return types
Tip
C++ requires return types to be compile-time determined.
- Clone the repository:
git clone https://github.com/huanhuanonly/cpp-kwargs.git- In your
.cppor.h:
#include "cpp-kwargs/include/kwargs.h"- Add the following to your
CMakeLists.txt:
set (CPP_KWARGS_REPOS "https://github.com/huanhuanonly/cpp-kwargs.git")
set (CPP_KWARGS_PATH "${CMAKE_SOURCE_DIR}/cpp-kwargs")
include (FetchContent)
if (NOT EXISTS ${CPP_KWARGS_PATH})
FetchContent_Declare (
CppKwargs
GIT_REPOSITORY ${CPP_KWARGS_REPOS}
GIT_TAG main
GIT_SHALLOW TRUE
SOURCE_DIR ${CPP_KWARGS_PATH}
)
FetchContent_MakeAvailable (CppKwargs)
endif()
add_subdirectory (${CPP_KWARGS_PATH})- Then in your
.cppor.h:
#include <kwargs.h>-
KWARGSKEY_CASE_INSENSITIVE
Enable case-insensitiveKwargsKey. -
KWARGSKEY_LITERAL_SUFFIX_WITHOUT_LEADING_UNDERSCORE
Use literal suffix without leading underscore:_opt→opt,_o→o. -
KWARGSKEY_LITERAL_SHORT_SUFFIX
Shorten suffix_optto_o.
How to define?
-
In your source file before
#include:#if !defined(KWARGSKEY_CASE_INSENSITIVE) # define KWARGSKEY_CASE_INSENSITIVE #endif #include "kwargs.h"
-
Or in your CMakeLists.txt before
add_subdirectory:set (KWARGSKEY_CASE_INSENSITIVE) add_subdirectory (${CPP_KWARGS_PATH})
-
All integral and floating-point types are mutually convertible.
-
Enum types are treated as their underlying integer types.
Tip
Even if the enum's underlying type is char/uchar, it will be treated as std::int8_t/std::uint8_t.
std::string⇄std::string_view.std::string/std::string_view⇄const char*.std::vector<char>/std::array<char>/std::string_view→const char*(null terminator not guaranteed).const char*/std::string/std::string_view→Integer.- Supports
0b/0o/0x/0hprefixes (case-insensitive), optional+/-sign. maxandmin(case-insensitive) as extreme values.- Unsigned types only accept leading
+.
- Supports
const char*/std::string/std::string_view→Floating point.- Supports scientific notation.
max,min,inf,infinity,nan,pi,e(case-insensitive) with optional+/-.
Integer/Floating point→std::string.const char*/std::string/std::string_view⇄char/uchar(uses first character, or\0if empty).bool→const char*/std::string/std::string_view("true"or"false")."true"/'t'(case-insensitive) →true."false"/'f'(case-insensitive) →false.- Iterable containers (with
.begin(),.end()and forward iterators) → Insertable containers.
Note
Both containers must have ::value_type. The value types may differ, and conversion will follow the rules above.
Insertable Containers
Must have one of the following members:.append().push_back().push().insert().push_front()
Important
The minimum compiler version requirements are as follows:
$\texttt{MSVC}\geq\text{v}{19.25}\left(\text{VS}{16.5}\right)$ $\texttt{GCC}\geq{10.1}$ $\texttt{Clang}\geq{11.0.0}$
-
Copyright
$2024\text{-}2025$ Yang Huanhuan (3347484963@qq.com). -
Created by Yang Huanhuan on
$December$ $29, 2024, 14:40:45$ . -
Goodbye
$2024$ , Hello$2025$ !