Skip to content

Latest commit

 

History

History
414 lines (303 loc) · 11.6 KB

File metadata and controls

414 lines (303 loc) · 11.6 KB

cpp-kwargs

STD-CPP STD-CPP CHINESE

HEADER-ONLY TEST-PASSING DOCS

License

简体中文

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.

Documentation

Features & Examples

Function Argument Passing

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'

Key Aliases:

  • "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>();
    }

Automatic Type Conversion:

  • 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]

Get Type Name:

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.

Type-based Behavior:

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.14

Class Constructor Arguments

struct 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 }
  • Font({ })

    • Construct: Font{ std::string(), 9, 0.00f, false }
  • Font({ {"name", "Arial"}, {"italic", true} })

    • Construct: Font{ std::string("Arial"), 9, 0.00f, true }
  • Font({ {"italic", "true"}, {"name", "Arial"} })

    • Construct: Font{ std::string("Arial"), 9, 0.00f, true }
  • Font({ "i"_opt="True", "faceName"_opt="Arial" })

    • Construct: Font{ std::string("Arial"), 9, 0.00f, true }
  • Font({ {"size", 18}, {"escapement", 45} })

    • Construct: Font{ std::string(), 18, 45.00f, false }
  • Font({ "size"_opt="18", "escapement"_opt="49.2" })

    • Construct: Font{ std::string(), 18, 49.20f, false }
  • Font({ "size"_opt=18.8, {"escapement", 49.2}, "i"_opt='t' })

    • Construct: Font{ std::string(), 18, 49.20f, true }

See more in TEST-PASSING.

Feature Comparison

  • 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 constexpr results 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.

Integration

Generic

  • Clone the repository:
git clone https://github.com/huanhuanonly/cpp-kwargs.git
  • In your .cpp or .h:
#include "cpp-kwargs/include/kwargs.h"

With CMake

  • 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 .cpp or .h:
#include <kwargs.h>

Optional Macros

  • KWARGSKEY_CASE_INSENSITIVE
    Enable case-insensitive KwargsKey.

  • KWARGSKEY_LITERAL_SUFFIX_WITHOUT_LEADING_UNDERSCORE
    Use literal suffix without leading underscore: _optopt, _oo.

  • KWARGSKEY_LITERAL_SHORT_SUFFIX
    Shorten suffix _opt to _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})

Supported Automatic Type Conversions

  • 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::stringstd::string_view.
  • std::string / std::string_viewconst char*.
  • std::vector<char> / std::array<char> / std::string_viewconst char* (null terminator not guaranteed).
  • const char* / std::string / std::string_viewInteger.
    • Supports 0b/0o/0x/0h prefixes (case-insensitive), optional +/- sign.
    • max and min (case-insensitive) as extreme values.
    • Unsigned types only accept leading +.
  • const char* / std::string / std::string_viewFloating point.
    • Supports scientific notation.
    • max, min, inf, infinity, nan, pi, e (case-insensitive) with optional +/-.
  • Integer / Floating pointstd::string.
  • const char* / std::string / std::string_viewchar / uchar (uses first character, or \0 if empty).
  • boolconst 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:
  1. .append()
  2. .push_back()
  3. .push()
  4. .insert()
  5. .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}$

Found a problem?