跳转至

第二章 函数参数的占位符类型

目录

背景: 使用auto和其他占位符来声明普通函数的参数.

  • 分类型模板参数的扩展
  • lambda模板

1. 什么是函数参数的占位符

占位符有点类似泛型,允许传递任意类型的参数,方便用户定义各种接口, 在cpp20版本之前, 仅允许lambda表达式, 在cpp20以后, 扩展到普通函数. 它的原理其实就是: 模板的快捷方式:

template <typename T> void lambda(T arg) { fmt::print("arg: {}\n", arg); }
void lambda_example() {
    lambda(42);
    lambda("Hello, World!");
}

2. 为什么要将auto作为参数

2.1 进行延迟类型检查

使用auto作为参数, 实现具有循环依赖关系的代码会容易很多. 对于以下例子的循环依赖, 本质上就是利用模板的延迟实例化特性.

namespace circular_dependency {
namespace compile_error {
struct C2; // forward declaration
struct C1 {
    void foo(const C2 &c2) const { // Ok
        // c2.print(); // error: C2 is incomplete, [compile error]
    }
    void print() const { fmt::print("C1::print()\n"); }
};

struct C2 {
    void foo(const C1 &c1) const { // Ok
        c1.print();                // Ok
    }
    void print() const { fmt::print("C2::print()\n"); }
};
} // namespace compile_error

namespace no_compile_error {
struct C2; // forward declaration
struct C1 {
    void foo(const C2 &c2) const; // Ok, forward declaration
    void print() const { fmt::print("C1::print()\n"); }
};

struct C2 {
    void foo(const C1 &c1) const {
        c1.print(); // Ok, C1 is complete
    }
    void print() const { fmt::print("C2::print()\n"); }
};

inline void C1::foo(const C2 &c2) const {
    c2.print(); // Ok
}
} // namespace no_compile_error

namespace no_compile_error_using_auto {
struct C2; // forward declaration

struct C1 {
    void foo(const std::same_as<C2> auto &c2) const { c2.print(); }
    void print() const { fmt::print("C1::print()\n"); }
};
struct C2 {
    void foo(const std::same_as<C1> auto &c1) const { c1.print(); }
    void print() const { fmt::print("C2::print()\n"); }
};
} // namespace no_compile_error_using_auto

} // namespace circular_dependency

void test_circular_dependency() {
    circular_dependency::no_compile_error::C1 c1;
    circular_dependency::no_compile_error::C2 c2;
    c1.foo(c2); // Ok, C2 is forward declared
    c2.foo(c1); // Ok, C1 is complete

    circular_dependency::no_compile_error_using_auto::C1 c1_auto;
    circular_dependency::no_compile_error_using_auto::C2 c2_auto;
    c1_auto.foo(c2_auto); // Ok, C2 is forward declared
    c2_auto.foo(c1_auto); // Ok, C1 is complete
}

2.2 为什么等价的函数模板需要显示调用

lambda函数可以被编译器推导出类型, 但函数模板不能自动作为函数指针进行传递, 必须显示指定模板参数或包装成lambda

namespace auto_and_lambda {
bool lessByNameFunc(const auto &c1, const auto &c2) { return c1.id < c2.id; }
// 等价于
template <typename T1, typename T2>
bool lessByNameFunc2(const T1 &c1, const T2 &c2) {
    return c1.id < c2.id;
}

void test_auto_and_lambda() {
    struct Item {
        int id;
    };
    std::vector<Item> items{Item{1}, Item{2}, Item{3}};
    // std::sort(items.begin(), items.end(), lessByNameFunc); // compile error
    std::sort(items.begin(), items.end(), lessByNameFunc<Item, Item>);
    std::sort(items.begin(), items.end(), [](const auto &c1, const auto &c2) {
        return c1.id < c2.id;
    }); // lambda 可以被编译器推导出合适的类型,
        // 并在lambda的上下文实例化这个函数对象
}
} // namespace auto_and_lambda

2.3 可变参数auto

理解变参模板的auto写法即可

namespace parameters_auto_pack {
void foo(auto... args) {
    ((fmt::print("{} ", args)), ...); // C++17 fold expression
    fmt::print("\n");
}

template <typename... Args> void foo2(Args... args) {
    ((fmt::print("{} ", args)), ...); // C++17 fold expression
    fmt::print("\n");
}
void test_parameters_auto_pack() {
    foo(1, 2, 3);
    foo("Hello", "World");
    foo2(1, 2, 3);
    foo2("Hello", "World");
}
} // namespace parameters_auto_pack

3. 完整示例代码

#include <fmt/core.h>

#include <algorithm>
#include <concepts>
#include <vector>

namespace less_cpp_20 {
auto lambda = [](auto arg) { fmt::print("arg: {}\n", arg); };
void lambda_example() {
    lambda(42);
    lambda("Hello, World!");
}
} // namespace less_cpp_20
namespace greater_cpp_20 {
void lambda(auto arg) { fmt::print("arg: {}\n", arg); }
void lambda_example() {
    lambda(42);
    lambda("Hello, World!");
}
} // namespace greater_cpp_20

namespace alias {
template <typename T> void lambda(T arg) { fmt::print("arg: {}\n", arg); }
void lambda_example() {
    lambda(42);
    lambda("Hello, World!");
}
} // namespace alias

namespace circular_dependency {
namespace compile_error {
struct C2; // forward declaration
struct C1 {
    void foo(const C2 &c2) const { // Ok
        // c2.print(); // error: C2 is incomplete, [compile error]
    }
    void print() const { fmt::print("C1::print()\n"); }
};

struct C2 {
    void foo(const C1 &c1) const { // Ok
        c1.print();                // Ok
    }
    void print() const { fmt::print("C2::print()\n"); }
};
} // namespace compile_error

namespace no_compile_error {
struct C2; // forward declaration
struct C1 {
    void foo(const C2 &c2) const; // Ok, forward declaration
    void print() const { fmt::print("C1::print()\n"); }
};

struct C2 {
    void foo(const C1 &c1) const {
        c1.print(); // Ok, C1 is complete
    }
    void print() const { fmt::print("C2::print()\n"); }
};

inline void C1::foo(const C2 &c2) const {
    c2.print(); // Ok
}
} // namespace no_compile_error

namespace no_compile_error_using_auto {
struct C2; // forward declaration

struct C1 {
    void foo(const std::same_as<C2> auto &c2) const { c2.print(); }
    void print() const { fmt::print("C1::print()\n"); }
};
struct C2 {
    void foo(const std::same_as<C1> auto &c1) const { c1.print(); }
    void print() const { fmt::print("C2::print()\n"); }
};
} // namespace no_compile_error_using_auto

} // namespace circular_dependency

void test_print() {
    less_cpp_20::lambda_example();
    greater_cpp_20::lambda_example();
    alias::lambda_example();
}

void test_circular_dependency() {
    circular_dependency::no_compile_error::C1 c1;
    circular_dependency::no_compile_error::C2 c2;
    c1.foo(c2); // Ok, C2 is forward declared
    c2.foo(c1); // Ok, C1 is complete

    circular_dependency::no_compile_error_using_auto::C1 c1_auto;
    circular_dependency::no_compile_error_using_auto::C2 c2_auto;
    c1_auto.foo(c2_auto); // Ok, C2 is forward declared
    c2_auto.foo(c1_auto); // Ok, C1 is complete
}

namespace auto_and_lambda {
bool lessByNameFunc(const auto &c1, const auto &c2) { return c1.id < c2.id; }
// 等价于
template <typename T1, typename T2>
bool lessByNameFunc2(const T1 &c1, const T2 &c2) {
    return c1.id < c2.id;
}

void test_auto_and_lambda() {
    struct Item {
        int id;
    };
    std::vector<Item> items{Item{1}, Item{2}, Item{3}};
    // std::sort(items.begin(), items.end(), lessByNameFunc); // compile error
    std::sort(items.begin(), items.end(), lessByNameFunc<Item, Item>);
    std::sort(items.begin(), items.end(), [](const auto &c1, const auto &c2) {
        return c1.id < c2.id;
    }); // lambda 可以被编译器推导出合适的类型,
        // 并在lambda的上下文实例化这个函数对象
}
} // namespace auto_and_lambda

namespace parameters_auto_pack {
void foo(auto... args) {
    ((fmt::print("{} ", args)), ...); // C++17 fold expression
    fmt::print("\n");
}

template <typename... Args> void foo2(Args... args) {
    ((fmt::print("{} ", args)), ...); // C++17 fold expression
    fmt::print("\n");
}
void test_parameters_auto_pack() {
    foo(1, 2, 3);
    foo("Hello", "World");
    foo2(1, 2, 3);
    foo2("Hello", "World");
}
} // namespace parameters_auto_pack

int main() {
    //
    test_print();
    test_circular_dependency();
    auto_and_lambda::test_auto_and_lambda();
    parameters_auto_pack::test_parameters_auto_pack();

    return 0;
}