C++可变参数

Netcan 2022-09-07 @Shanghai

自我介绍

  • 96年出生,18年毕业于合肥工业大学本科

  • 某大厂高级工程师

  • 机工社 《C++20高级编程》作者

  • 知乎 《魅力C++》 专栏作者

  • 兴趣:CS, OO, FP, Design, Coding, Writing

  • Skills: C/C++/Rust, Haskell/Scheme, Bash/Python

议程

  • 各语言中的可变参数

  • C++中的可变参数

  • 可变模板参数包

  • 参数包展开、遍历

  • 元编程灵魂:折叠表达式

  • 遍历tuple

  • 构建eDSL(动态 vs 静态)

  • 生成switch-case结构

  • 剖析std::thread实现

  • 静态反射 Reflection TS

各语言中的可变参数

预处理宏

#define add_1(a) a
#define add_2(a, b) a + b
#define add_3(a, b, c) a + add_2(b, c)

#define add(...) PASTE(add_, GET_ARG_COUNT(__VA_ARGS__)) (__VA_ARGS__)

Rust声明宏

macro_rules! add {
    ($($x:expr), *) => {
        0 $( + $x )*
    }
}
add!(1, 2, 3, 4)

各语言中的可变参数

C语言

int add(int n, ...) {
    va_list args;
    va_start(args, n);
    int ret = 0;

    while(n-- > 0)
        ret += va_arg(args, int);

    va_end(args);
    return ret;
}

各语言中的可变参数

Bash语言

function add {
    sum=0
    for v in "$@"; do
        sum=$((sum+$v))
    done
    echo $sum
}

add 1 2 3

Python语言

def add(*args):
    return sum(args)

JavaScript语言

function add(...args) {
    return args.reduce((c, p) => c + p, 0)
}

各语言中的可变参数

特点

  • 存在参数包的概念

  • 存在展开的概念

  • 支持遍历的功能

  • 库专用特性

缺点

  • 预处理宏难以移植

  • 缺少类型信息

  • 类型不安全

  • 性能差

C++中的可变参数

template<typename... T>
constexpr auto add(T... v) {
    return (0 + ... + v);
}
  • C++11起支持可变模板参数

  • C++17起支持折叠表达式

可变模板参数包

template<typename... Args>
void f(Args... args) {
    constexpr auto s1 = sizeof...(Args); // 模板参数包
    constexpr auto s2 = sizeof...(args); // 函数参数包
    static_assert(s1 == s2);
}
  • 模板参数包:存放函数参数包对应的类型

  • 函数参数包:存放传递给函数的实参包

参数包展开

template<typename... Args>
void f(Args... args) {
    std::tuple<Args...> tp = // 展开模板参数包
        std::make_tuple(args...); // 展开函数参数包
}

f(1, "2", 3.0) 展开后的结果 https://cppinsights.io/s/b78454ba

template<>
void f<int, const char *, double>(int __args0, const char * __args1, double __args2) {
  std::tuple<int, const char *, double> tp =
    std::make_tuple(__args0, __args1, __args2);
}

参数包遍历

递归遍历:

void print() { }

template<typename A, typename... Args>
void print(const A& arg, const Args&... args) {
    std::cout << arg << std::endl;
    print(args...); // 递归与参数包展开
}

折叠表达式

  • 右折叠:(pack op …​ [op init])

  • 左折叠:([init op] …​ op pack)

template<int... Is> // 右折叠
constexpr int rsub = (Is - ... - 0); // (Is - ...)
template<int... Is> // 左折叠
constexpr int lsub = (0 - ... - Is);
// (1 - (2 - (3 - (4 - (5 - 0)))))
static_assert(rsub<1,2,3,4,5> == 3);
// (((((0 - 1) - 2) - 3) - 4) - 5)
static_assert(lsub<1,2,3,4,5> == -15);

利用折叠表达式遍历:

template<typename... Args>
void print(const Args&... args) {
    ((std::cout << args << std::endl), ...); // 逗号表达式、折叠表达式
}

参数包展开 vs 折叠表达式

template<typename... Ts>
void f(Ts... args) {
    auto v  = {args  ...}; // 参数包展开
    auto v_ = (args, ...); // 折叠表达式
}
template<>
void f<int, int, int, int>(int __args0, int __args1, int __args2, int __args3) {
    std::initializer_list<int> v = {__args0, __args1, __args2, __args3};
    int v_ = __args0 , (__args1 , (__args2 , __args3));
}

遍历tuple

C++11 特化、递归

template<size_t N>
struct dumpImpl {
    template<typename Tup>
    void operator()(const Tup& tp) const {
        dumpImpl<N-1>{} (tp);
        std::cout << std::get<N>(tp) << " ";
    }
};

template<>
struct dumpImpl<0> {
    template<typename Tup>
    void operator()(const Tup& tp) const {
        std::cout << std::get<0>(tp) << " ";
    }
};

template<typename... Ts>
void dump(const std::tuple<Ts...>& tp) {
    dumpImpl<sizeof...(Ts) - 1>{} (tp);
}
auto tp = std::make_tuple(1, 2.0, 3.0f);
dump(tp); // 1 2 3

遍历tuple

C++14 integer_sequence

template<typename Tup, size_t... I>
void dumpImpl(const Tup& tp, std::index_sequence<I...>) {
    auto l = { ((std::cout << std::get<I>(tp) << " "), 0)... };
}

template<typename... Ts>
void dump(const std::tuple<Ts...>& tp) {
    std::make_index_sequence<sizeof...(Ts)> seqs;
    dumpImpl(tp, seqs);
}

C++20 折叠表达式 + apply + auto函数参数

template<typename... Ts>
void dump(const std::tuple<Ts...>& tp) {
    std::apply([](const auto&... args) {
        ((std::cout << args << " "),...);
    }, tp);
}

构建eDSL

auto v = html(ul( li("Coffee")
                , li("Tea")
                , li("Milk")),
              ol( li("hello"),
                  li("world")));

HtmlDumpper{std::cout}(v);

输出

<html>
<ul>
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
</ul>
<ul>
<li>hello</li>
<li>world</li>
</ul>
</html>

构建eDSL

struct LiTag {
    std::string_view content;
};

auto li(std::string_view vs) {
    return LiTag { vs };
}

struct UlTag {
    template<typename... Args>
    UlTag(Args... arg) {
        (liTag_.emplace_back(arg), ...);
    }
    std::vector<LiTag> liTag_;
};

template<typename... LI>
auto ul(LI... li) {
    return UlTag { li... };
}

构建eDSL

struct OlTag {
    template<typename... Args>
    OlTag(Args... arg) {
        (liTag_.emplace_back(arg), ...);
    }
    std::vector<LiTag> liTag_;
};

template<typename... LI>
auto ol(LI... li) {
    return UlTag { li... };
}

struct HtmlTag {
    template<typename... Es>
    HtmlTag(Es... es) {
        (elems_.emplace_back(es), ...);
    }
    using Elem = std::variant<UlTag, OlTag>;
    std::vector<Elem> elems_;
};

template<typename... E>
auto html(E... e) {
    return HtmlTag { e... };
}

构建eDSL:元编程

constexpr auto v = html(ul( li<"Coffee">
                          , li<"Tea">
                          , li<"Milk">),
                        ol( li<"hello">,
                            li<"world">));
htmlDumpper(v);

构建eDSL:元编程

template<typename... LI>
struct UlTag { };

template<typename... UI>
struct OlTag { };

template<typename... Es>
struct HtmlTag { };

template<StringLiteral content>
struct LiTag { };
template<typename... E>
constexpr auto html(E...) {
    return HtmlTag<E...> {};
}

template<typename... LI>
constexpr auto ul(LI...) {
    return UlTag<LI...>{};
}

template<typename... LI>
constexpr auto ol(LI...) {
    return OlTag<LI...>{};
}

template<StringLiteral content>
constexpr auto li = LiTag<content>{};

生成switch-case结构

通常的std::get使用

auto tp = std::make_tuple(1, 2.0, 3.0f);
std::get<2>(tp); // 3.0f

希望提供动态的get

template<typename... Ts>
const void* get(const std::tuple<Ts...>& tp, size_t index);

get(tp, 2);

生成switch-case结构

预期:

template<typename... Ts>
const void* get(const std::tuple<Ts...>& tp, size_t index) {
    switch (index) {
        case 0: return &std::get<0>(tp);
        case 1: return &std::get<1>(tp);
        case 2: return &std::get<2>(tp);
        default: return nullptr;
    }
}

使用折叠表达式:

template<typename... Ts>
const void* get(const std::tuple<Ts...>& tp, size_t index) {
    auto getE = [&]<size_t I>(std::in_place_index_t<I>, size_t index) -> const void* {
        return I == index ? &std::get<I>(tp) : nullptr;
    };

    const void* res = nullptr;
    [&]<size_t... Is>(std::index_sequence<Is...>) {
        (((res = getE(std::in_place_index_t<Is>{}, index)) != nullptr) || ...);
    }(std::make_index_sequence<sizeof...(Ts)>{});

    return res;
}

std::thread实现

std::thread 原型

template<typename Function, typename... Args>
explicit thread(Function&& f, Args&&... args);

// example
std::thread th(f, a, b);
th.join();

pthread 原型

int pthread_create(pthread_t *thread,
                   const pthread_attr_t *attr,
                   void *(*start_routine)(void *),
                   void *arg);

std::thread实现

struct thread {
    template<typename F, typename... Args>
    explicit thread(F f, Args... args) {
        using FnArgs = std::tuple<F, std::tuple<Args...>>;
        std::unique_ptr<FnArgs> argsTup { new FnArgs(f, {args...}) };

        int rc = pthread_create(&handle_, nullptr, [](void* fnArgs) -> void *{
            std::unique_ptr<FnArgs> argsTup { static_cast<FnArgs*>(fnArgs) };
            auto& [fn, args] = *argsTup;
            std::apply(fn, args);
            return nullptr;
        }, argsTup.get());

        if (rc == 0) argsTup.release();
        else throw;
    }
    void join() {
        pthread_join(handle_, nullptr);
    }
private:
    pthread_t handle_;
};

静态反射 Reflection TS

定义数据结构

struct Point {
    double x;
    double y;
};

struct Rect {
    Point p1;
    Point p2;
};

template<typename T>
void dump(const T& v, int depth = 0);
dump(Rect{{1.2, 3.4}, {4.5, 5.6}});

输出结果

(Point) p1:
    (double) x: 1.2
    (double) y: 3.4
(Point) p2:
    (double) x: 4.5
    (double) y: 5.6

静态反射 Reflection TS

template<typename... T> struct TypeList {};

template<typename T>
void dump(const T& v, int depth = 0) {
    auto indent = [depth] {
        for (int i = 0; i < depth; ++i) std::cout << "    ";
    };

    using TMembers = refl::get_data_members_t<reflexpr(T)>;
    [&]<typename... M>(TypeList<M...>) {
        ([&]<typename M_>(M_) {
            using MTypeObj = refl::get_type_t<M_>;
            using MType = refl::get_reflected_type_t<MTypeObj>;
            constexpr auto type_name = refl::get_name_v<MTypeObj>;
            constexpr auto field_name = refl::get_name_v<M_>;
            constexpr auto mptr = refl::get_pointer_v<M_>;
            indent();
            std::cout << "(" << type_name << ") " << field_name << ": ";
            if constexpr(std::is_class_v<MType>) {
                std::cout << std::endl;
                dump(v.*mptr, depth + 1);
            } else {
                std::cout << v.*mptr << std::endl;
            }
        }(M{}), ...);
    }(refl::unpack_sequence_t<TypeList, TMembers>{});
}

参考资料