Skip to content

Implement Python's **kwargs style parameter passing in C++. 在 C++ 中实现 Python 的 **Kwargs 风格传参。

License

Notifications You must be signed in to change notification settings

huanhuanonly/cpp-kwargs

Repository files navigation

cpp-kwargs

STD-CPP ENGLISH VIEW-CODE EXAMPLES-MORE DOCS

cpp-kwargs 是一个能在 C++ 中实现类似于 Python **kwargs 传参的库。

它通过 C++ 强大的模板编程封装了一个 Kwargs 类以此来实现了该功能。

Python 中的 **kwargs

在 Python 中, **Kwargs 用于函数定义时接受任意数量的关键字参数。它将所有通过 Key=Value 形式传递的参数封装成一个字典,在函数内部可以通过 kwargs 访问这些参数,**kwargs 使得函数能够灵活地接受不定数量的关键字参数,提升了代码的可扩展性。官方文档

文档

  • Python (**kwargs)cpp-kwargs 都支持的:

    • 支持 按任意顺序排列的 键;
    • 支持 缺少的多余的 键;
    • 支持 限定键名
    • 支持 任意类型的 值;
    • 保留 原始值的类型信息
  • cpp-kwargs 额外支持的:

    • 自动的 类型转换(传入类型和传出的类型不一致时);
    • 较小的开销,Kwargs 的内部会尽可能地使用 constexpr,将在 编译期得到结果 (如果满足条件的话);
    • 键名不区别大小写(可选的);

Tip

推荐使用 C++ $20$,在 C++ $20$ 中,STL 更多的被声明为 constexpr,代码的 编写测试 皆在 C++ $20$ 中完成。

  • Python (**kwargs) 支持的:

    • 动态的返回值类型;

Tip

C++ 的返回值类型必须在编译时确定。

应用在函数中

函数原型
  • In Python:

    # 任意的键名
    def func(**kwargs): ...
    
    # 限定键名(带默认值)
    def func(*, name='empty_name', old=0): ...
  • In C++:

    // 任意的键名
    auto func(Kwargs<> kwargs = {})
    {...}
    
    // 限定键名(无需带默认值)
    auto func(Kwargs<"name"_opt, "old"_opt> kwargs = {})
    {...}
外部调用
  • In Python:

    # 正常
    func(name='huanhuanonly', old=18)
    
    # 非预期的类型
    func(name='huanhuanonly', old='18')
    
    # 相反的顺序
    func(old=18, name='huanhuanonly')
    
    # 空的参数
    func()
  • In C++:

    // 正常
    func({ {"name", "huanhuanonly"}, {"old", 18} });
    
    // 非预期的类型
    func({ {"name", "huanhuanonly"}, {"old", "18"} });
    
    // 相反的顺序
    func({ {"old", 18}, {"name", "huanhuanonly"} });
    
    // 空的参数
    func()
内部获取值
  • 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()
    // 等效于
    // if 'name' in kwargs

应用在类的构造函数中

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

以下构造 Font 的方式都是有效的:

  • Font()

    • 同等于: Font{ std::string(), 9, 0.00f, false }
  • Font({ })

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

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

    • 同等于: Font{ std::string("Arial"), 9, 0.00f, true }
  • Font({ {"i", "True"}, {"faceName", "Arial"} })

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

    • 同等于: Font{ std::string(), 18, 45.00f, false }
  • Font({ {"size", "18"}, {"escapement", "49.2"} })

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

    • 同等于: Font{ std::string(), 18, 49.20f, true }

简单示例:Python 和 C++ 中的 printList()

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

调用:

  • In Python

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

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

EXAMPLES-MORE 中可以看到更多的使用方式。

导入到自己的项目中

克隆该仓库

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

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

该项目只需要一个头文件即可运行。

设置 KwargsKey 不区分大小写

  • #include "CppKwargs.h" 前定义 KWARGSKEY_CASE_INSENSITIVE

    #ifndef KWARGSKEY_CASE_INSENSITIVE
    #  define KWARGSKEY_CASE_INSENSITIVE
    #endif
    
    #include "CppKwargs.h"
  • 或者,在你的项目中的 CMakeList.txt 文件中添加以下行:

    target_compile_definitions (YourExecutable PRIVATE KWARGSKEY_CASE_INSENSITIVE)

支持的内置类型自动转换

  • 所有的整型和浮点型之间的互相转换。

  • 对于所有枚举类型 enum 视为其底层类型(整型)。

Tip

即使 enum 的底层类型是 char / uchar,它也会作为一个整型(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*(并不保证有 \0 结束符)。

  • 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(取首字符,空则返回 \0)。

  • 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

  • 可迭代的容器(拥有 .begin().end()前向迭代器$\longrightarrow$ 可插入的容器。

Note

两个容器必须都要包含 ::value_type 类型,值类型不需要一致,不一致时将按照以上规则进行转换。

可插入的容器 拥有以下成员函数之一(按顺序):
  1. .append()
  2. .push_back()
  3. .push()
  4. .insert()
  5. .push_front()

About

Implement Python's **kwargs style parameter passing in C++. 在 C++ 中实现 Python 的 **Kwargs 风格传参。

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published