Skip to content

Latest commit

 

History

History
360 lines (251 loc) · 10.1 KB

README_English.md

File metadata and controls

360 lines (251 loc) · 10.1 KB

cpp-kwargs

STD-CPP CHINESE VIEW-CODE EXAMPLES-MORE DOCS

cpp-kwargs is a library that implements Python-like **kwargs parameter passing in C++.

It encapsulates a Kwargs class using C++'s powerful template programming to achieve this functionality.

Python's **kwargs

In Python, **Kwargs is used in function definitions to accept any number of keyword arguments. It wraps all arguments passed as Key=Value into a dictionary, which can be accessed via kwargs inside the function. **kwargs allows functions to flexibly accept a variable number of keyword arguments, enhancing code scalability. Official Documentation.

Documentation

  • Python (**kwargs) and cpp-kwargs both support:

    • Keys in any order;
    • Missing or extra keys;
    • Limited key names;
    • Values of any type;
    • Preserving original value types;
  • cpp-kwargs additionally supports:

    • Automatic type conversion (when input and output types are mismatched);
    • Smaller overhead, Kwargs uses constexpr internally as much as possible, providing results at compile time (if conditions are met);
    • Key names are case-insensitive (optional);

Tip

It's recommended to use C++ $20$, as the STL in C++ $20$ is more likely to be declared as constexpr, and code writing and testing are all done in C++ $20$.

  • Only Python (**kwargs) supports:

    • Dynamic return value types;

Tip

In C++, the return type must be determined at compile time.

Application in Functions

Function Prototypes
  • In Python:

    # Any key name
    def func(**kwargs): ...
    
    # Limited key names (with default values)
    def func(*, name='empty_name', old=0): ...
  • In C++:

    // Any key name
    auto func(Kwargs<> kwargs = {})
    {...}
    
    // Limited key names (no need for default values)
    auto func(Kwargs<"name"_opt, "old"_opt> kwargs = {})
    {...}
External Calls
  • In Python:

    # Normal
    func(name='huanhuanonly', old=18)
    
    # Unexpected type
    func(name='huanhuanonly', old='18')
    
    # Reversed order
    func(old=18, name='huanhuanonly')
    
    # Empty arguments
    func()
  • In C++:

    // Normal
    func({ {"name", "huanhuanonly"}, {"old", 18} });
    
    // Unexpected type
    func({ {"name", "huanhuanonly"}, {"old", "18"} });
    
    // Reversed order
    func({ {"old", 18}, {"name", "huanhuanonly"} });
    
    // Empty arguments
    func()
Internal Value Access
  • 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()
    // Equivalent to
    // if 'name' in kwargs

Application in Class Constructors

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))
  { }
};

The following constructors for People are all valid:

  • Font()

    • Equal to: Font{ std::string(), 9, 0.00f, false }
  • Font({ })

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

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

    • Equal to: Font{ std::string("Arial"), 9, 0.00f, true }
  • Font({ {"i", "True"}, {"faceName", "Arial"} })

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

    • Equal to: Font{ std::string(), 18, 45.00f, false }
  • Font({ {"size", "18"}, {"escapement", "49.2"} })

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

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

Simple Example: printList() in Python and C++

  • In Python

    def printList(value: list, /, *, sep = ', ', end = '\n'):
    
      if len(value) == 0:
        return
    
      for i in range(len(value) - 1):
        print(value[i], end=sep)
    
      print(value[-1], end=end)
  • In C++

    void printList(
      const std::vector<int>& value,
      Kwargs<"sep"_opt, "end"_opt> kwargs = { })
    {
      if (value.empty())
        return;
    
      for (std::size_t i = 0; i < value.size() - 1; ++i)
        std::cout << value[i],
        std::cout << kwargs["sep"].valueOr<std::string_view>(", ");
    
      std::cout << value.back();
      std::cout << kwargs["end"].valueOr<std::string_view>("\n");
    }

Call:

  • In Python

    printList([1, 4, 3, 3, 2, 2, 3], sep=' | ', end='.')
  • In C++

    printList(
      {1, 4, 3, 3, 2, 2, 3},
      { {"sep", " | "}, {"end", '.'} });

For more usage examples, click EXAMPLES-MORE.

Importing into Your Project

Clone this repository

git clone https://github.com/huanhuanonly/cpp-kwargs.git

Configure in CMakeList.txt

  • CMakeList.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()

include_directories (${CPP_KWARGS_PATH})
  • main.cpp
#include <CppKwargs.h>

Tip

This project only requires a single header file to run.

Set KwargsKey to be case-insensitive

  • Define KWARGSKEY_CASE_INSENSITIVE before #include "CppKwargs.h":

    #ifndef KWARGSKEY_CASE_INSENSITIVE
    #  define KWARGSKEY_CASE_INSENSITIVE
    #endif
    
    #include "CppKwargs.h"
  • Alternatively, add the following line to your project's CMakeList.txt file:

    target_compile_definitions (YourExecutable PRIVATE KWARGSKEY_CASE_INSENSITIVE)

Supported Built-in Type Automatic Conversion

  • All integer and floating-point type conversions.

  • For all enumeration types enum is considered to be its underlying type (integer).

Tip

Even if the underlying type of enum is char / uchar, it will be treated as an integer (std::int8_t / std::uint8_t).

  • std::string $\longleftrightarrow$ std::string_view.

  • std::string / std::string_view $\longleftrightarrow$ const char*.

  • std::vector<char> / std::array<char> / std::string_view $\longrightarrow$ const char* (does not guarantee \0 terminator).

  • const char* / std::string / std::string_view $\longrightarrow$ Integer / Floating point.

  • Integer / Floating point $\longrightarrow$ std::string.

  • const char* / std::string / std::string_view $\longleftrightarrow$ char / uchar (takes the first character, returns \0 if empty).

  • bool $\longrightarrow$ const char* / std::string / std::string_view ("true" or "false").

  • "true" / "True" / "TRUE" / 't' / 'T' $\longrightarrow$ true.

  • "false" / "False" / "FALSE" / 'f' / 'F' $\longrightarrow$ false.

  • Iterable containers (with .begin(), .end(), and forward-iterator) $\longrightarrow$ Insertable containers.

Note

Both containers must have ::value_type, but the value types don't need to match. If they don't, conversion will follow the above rules.

Insertable containers

Has one of the following member functions (in order):

  1. .append()
  2. .push_back()
  3. .push()
  4. .insert()
  5. .push_front()