19.4 命名参数模式(Named Parameters)

相关章节

在本节中你将学到…

  • 命名参数模式的设计动机和优势
  • CGAL parameters的实现原理
  • 如何编写支持命名参数的函数
  • 命名参数与默认参数的区别
  • 实际应用中的最佳实践

19.4.1 概念解释

什么是命名参数模式?

命名参数模式(Named Parameters Pattern)是一种编程技术,允许函数调用者通过参数名而非位置来指定参数。这在参数众多、大部分有默认值的情况下特别有用。

类比理解: 想象你在一家高级餐厅点餐。传统方式(位置参数)是:“我要第3道菜、加辣、不要洋葱、用橄榄油”。而命名参数方式是:“我要宫保鸡丁,辣度:微辣,配料:去掉洋葱,用油:橄榄油”。后者更清晰,也不容易出错。

命名参数 vs 位置参数

// 传统位置参数(容易混淆)
process_mesh(mesh, true, false, 0.5, 100, true, false);
// 问题:这些布尔值和数字代表什么?
 
// 命名参数(清晰可读)
process_mesh(mesh, 
             CGAL::parameters::preserve_features(true),
             CGAL::parameters::edge_sensitivity(0.5),
             CGAL::parameters::number_of_iterations(100));

命名参数的优势

  1. 可读性:代码自我文档化
  2. 灵活性:参数顺序无关
  3. 可扩展:添加新参数不影响现有代码
  4. 可选性:轻松跳过某些参数使用默认值

19.4.2 为什么需要命名参数?

问题1:参数过多的函数

CGAL的许多算法有大量可选参数:

// 假设的网格处理函数(不使用命名参数)
template <typename Mesh>
void remesh(Mesh& mesh,
            double target_edge_length,
            int num_iterations = 3,
            bool protect_constraints = false,
            double smoothness = 0.5,
            bool verbose = false,
            int vertex_count_target = 0,
            bool preserve_boundary = true);
 
// 调用时:想要修改smoothness,但必须指定前面的所有参数
remesh(mesh, 0.5, 3, false, 0.8);  // 混乱!

问题2:布尔参数陷阱

// 反模式:连续的布尔参数
void configure(bool enable_logging, bool use_cache, bool async_mode);
 
configure(true, false, true);  // 难以阅读和理解

问题3:向后兼容性

添加新参数时,命名参数不会破坏现有代码:

// 添加新参数 new_option
process_mesh(mesh, 
             CGAL::parameters::preserve_features(true));
             // 新参数有默认值,不影响旧代码

19.4.3 代码示例

基础示例:简单的命名参数实现

#include <iostream>
#include <string>
 
// ============================================
// 简单的命名参数实现
// ============================================
 
// 参数标签(用于标识参数类型)
struct color_tag {};
struct size_tag {};
struct visible_tag {};
 
// 命名参数类
template <typename Tag, typename T>
class NamedParam {
    T value_;
public:
    explicit NamedParam(T value) : value_(value) {}
    T value() const { return value_; }
};
 
// 创建命名参数的辅助函数
template <typename T>
NamedParam<color_tag, T> color(T c) {
    return NamedParam<color_tag, T>(c);
}
 
template <typename T>
NamedParam<size_tag, T> size(T s) {
    return NamedParam<size_tag, T>(s);
}
 
template <typename T>
NamedParam<visible_tag, T> visible(T v) {
    return NamedParam<visible_tag, T>(v);
}
 
// 参数包类(递归存储多个参数)
class NoParam {};
 
template <typename Tag, typename T, typename Base = NoParam>
class ParamPack : public Base {
    T value_;
public:
    ParamPack(T value, const Base& base = Base()) 
        : Base(base), value_(value) {}
    
    T get(Tag) const { return value_; }
    
    // 添加新参数
    template <typename NewTag, typename NewT>
    ParamPack<NewTag, NewT, ParamPack> operator,(const NamedParam<NewTag, NewT>& np) {
        return ParamPack<NewTag, NewT, ParamPack>(np.value(), *this);
    }
};
 
// 从参数包中提取参数(带默认值)
template <typename Tag, typename T, typename Base>
T get_param(const ParamPack<Tag, T, Base>& pack, Tag, T default_val) {
    return pack.get(Tag());
}
 
template <typename Tag, typename T>
T get_param(const NoParam&, Tag, T default_val) {
    return default_val;
}
 
// 使用命名参数的函数
void draw_shape(const ParamPack<NoParam>& pack = NoParam()) {
    // 提取参数(使用默认值)
    std::string c = get_param(pack, color_tag(), std::string("red"));
    int s = get_param(pack, size_tag(), 10);
    bool v = get_param(pack, visible_tag(), true);
    
    std::cout << "Drawing shape: color=" << c 
              << ", size=" << s 
              << ", visible=" << (v ? "true" : "false") << std::endl;
}
 
// 支持命名参数的模板版本
template <typename Pack>
void draw_shape(const Pack& pack) {
    std::string c = get_param(pack, color_tag(), std::string("red"));
    int s = get_param(pack, size_tag(), 10);
    bool v = get_param(pack, visible_tag(), true);
    
    std::cout << "Drawing shape: color=" << c 
              << ", size=" << s 
              << ", visible=" << (v ? "true" : "false") << std::endl;
}
 
// 辅助函数:开始构建参数包
template <typename Tag, typename T>
ParamPack<Tag, T> make_params(const NamedParam<Tag, T>& np) {
    return ParamPack<Tag, T>(np.value());
}
 
int main() {
    // 使用默认参数
    draw_shape();
    
    // 使用命名参数
    draw_shape(make_params(color(std::string("blue"))));
    
    // 多个命名参数
    auto params = ParamPack<visible_tag, bool>(false,
                 ParamPack<size_tag, int>(20,
                 ParamPack<color_tag, std::string>("green")));
    draw_shape(params);
    
    return 0;
}

中级示例:改进的命名参数系统

#include <iostream>
#include <string>
#include <utility>
#include <type_traits>
 
// ============================================
// 改进的命名参数系统
// ============================================
 
namespace parameters {
 
// 参数未找到标记
struct ParamNotFound {};
 
// 基础参数类(无参数)
struct NoParam {
    typedef NoParam base;
};
 
// 参数实现(递归链表结构)
template <typename Tag, typename T, typename Base = NoParam>
struct ParamImpl : Base {
    T value;
    
    ParamImpl(T v, const Base& b = Base()) : Base(b), value(std::move(v)) {}
    
    typedef ParamImpl<Tag, T, Base> self;
    typedef Base base;
};
 
// 参数查找辅助类
template <typename ParamPack, typename QueryTag>
struct GetParam;
 
// 找到匹配的标签
template <typename T, typename Tag, typename Base>
struct GetParam<ParamImpl<Tag, T, Base>, Tag> {
    typedef T type;
    static const T& get(const ParamImpl<Tag, T, Base>& p) { return p.value; }
};
 
// 继续递归查找
template <typename T, typename Tag, typename Base, typename QueryTag>
struct GetParam<ParamImpl<Tag, T, Base>, QueryTag> {
    typedef typename GetParam<Base, QueryTag>::type type;
    static const type& get(const ParamImpl<Tag, T, Base>& p) {
        return GetParam<Base, QueryTag>::get(static_cast<const Base>(p));
    }
};
 
// 未找到
template <typename QueryTag>
struct GetParam<NoParam, QueryTag> {
    typedef ParamNotFound type;
};
 
// 带默认值的参数获取
template <typename ParamPack, typename Tag, typename Default>
auto get_param(const ParamPack& pack, Tag, const Default& default_val)
    -> typename std::conditional<
        std::is_same<typename GetParam<ParamPack, Tag>::type, ParamNotFound>::value,
        Default,
        typename GetParam<ParamPack, Tag>::type
    >::type
{
    return get_param_impl(pack, Tag(), default_val, 
                         std::is_same<typename GetParam<ParamPack, Tag>::type, ParamNotFound>());
}
 
template <typename ParamPack, typename Tag, typename Default>
Default get_param_impl(const ParamPack&, Tag, const Default& default_val, std::true_type) {
    return default_val;
}
 
template <typename ParamPack, typename Tag, typename Default>
const typename GetParam<ParamPack, Tag>::type& 
get_param_impl(const ParamPack& pack, Tag, const Default&, std::false_type) {
    return GetParam<ParamPack, Tag>::get(pack);
}
 
// 命名参数类
template <typename Tag>
struct NamedParam {
    template <typename T>
    ParamImpl<Tag, T> operator=(T value) const {
        return ParamImpl<Tag, T>(std::move(value));
    }
    
    // 支持直接传入值(使用括号语法)
    template <typename T>
    ParamImpl<Tag, T> operator()(T value) const {
        return ParamImpl<Tag, T>(std::move(value));
    }
};
 
// 参数组合操作符
template <typename Tag1, typename T1, typename Tag2, typename T2>
ParamImpl<Tag2, T2, ParamImpl<Tag1, T1>> 
operator,(const ParamImpl<Tag1, T1>& p1, const ParamImpl<Tag2, T2>& p2) {
    return ParamImpl<Tag2, T2, ParamImpl<Tag1, T1>>(p2.value, p1);
}
 
// 预定义参数标签
struct color_tag {};
struct size_tag {};
struct visible_tag {};
struct opacity_tag {};
 
// 全局参数对象(用于命名参数语法)
constexpr NamedParam<color_tag> color;
constexpr NamedParam<size_tag> size;
constexpr NamedParam<visible_tag> visible;
constexpr NamedParam<opacity_tag> opacity;
 
} // namespace parameters
 
// ============================================
// 使用命名参数的类
// ============================================
 
class Shape {
public:
    std::string color = "red";
    int size = 10;
    bool visible = true;
    double opacity = 1.0;
    
    // 默认构造函数
    Shape() = default;
    
    // 命名参数构造函数
    template <typename ParamPack>
    Shape(const ParamPack& params) {
        using namespace parameters;
        color = get_param(params, color_tag(), color);
        size = get_param(params, size_tag(), size);
        visible = get_param(params, visible_tag(), visible);
        opacity = get_param(params, opacity_tag(), opacity);
    }
    
    void print() const {
        std::cout <> "Shape: color=" << color 
                  << ", size=" << size
                  << ", visible=" << (visible ? "true" : "false")
                  << ", opacity=" << opacity << std::endl;
    }
};
 
int main() {
    using namespace parameters;
    
    // 使用默认参数
    Shape s1;
    s1.print();
    
    // 使用单个命名参数
    Shape s2(color = "blue");
    s2.print();
    
    // 使用多个命名参数(任意顺序)
    Shape s3((size = 20, color = "green", visible = false));
    s3.print();
    
    // 使用括号语法
    Shape s4(color("purple"), opacity(0.5));
    s4.print();
    
    return 0;
}

高级示例:完整的CGAL风格命名参数

#include <iostream>
#include <string>
#include <utility>
#include <type_traits>
#include <functional>
 
// ============================================
// 完整的CGAL风格命名参数实现
// ============================================
 
namespace CGAL {
namespace internal_np {
 
struct No_property {};
struct Param_not_found {};
 
// 参数实现(递归嵌套)
template <typename T, typename Tag, typename Base>
struct Named_params_impl : Base {
    // 如果T可复制,直接存储;否则存储引用包装器
    typename std::conditional<std::is_copy_constructible<T>::value,
                                T, 
                                std::reference_wrapper<const T>>::type v;
    
    Named_params_impl(const T& val, const Base& b) : Base(b), v(val) {}
    
    typedef Named_params_impl<T, Tag, Base> base;
};
 
// 特化:基础情况
template <typename T, typename Tag>
struct Named_params_impl<T, Tag, No_property> {
    typename std::conditional<std::is_copy_constructible<T>::value,
                                T,
                                std::reference_wrapper<const T>>::type v;
    
    explicit Named_params_impl(const T& val) : v(val) {}
    
    typedef Named_params_impl<T, Tag, No_property> base;
};
 
// 参数查找
template <typename ParamPack, typename QueryTag>
struct Get_param;
 
// 找到匹配
template <typename T, typename Tag, typename Base>
struct Get_param<Named_params_impl<T, Tag, Base>, Tag> {
    typedef T type;
    typedef const T& reference;
};
 
template <typename T, typename Tag>
struct Get_param<Named_params_impl<T, Tag, No_property>, Tag> {
    typedef T type;
    typedef const T& reference;
};
 
// 递归查找
template <typename T, typename Tag, typename Base, typename QueryTag>
struct Get_param<Named_params_impl<T, Tag, Base>, QueryTag> 
    : Get_param<Base, QueryTag> {};
 
// 未找到
template <typename QueryTag>
struct Get_param<No_property, QueryTag> {
    typedef Param_not_found type;
    typedef Param_not_found reference;
};
 
// 带默认值的查找
template <typename QueryTag, typename ParamPack, typename Default>
struct Lookup_named_param_def {
    typedef typename Get_param<ParamPack, QueryTag>::type NP_type;
    typedef typename Get_param<ParamPack, QueryTag>::reference NP_reference;
    
    typedef typename std::conditional<
        std::is_same<NP_type, Param_not_found>::value,
        Default,
        NP_type
    >::type type;
    
    typedef typename std::conditional<
        std::is_same<NP_reference, Param_not_found>::value,
        Default&,
        NP_reference
    >::type reference;
};
 
} // namespace internal_np
 
// 命名参数类
template <typename T = bool, typename Tag = internal_np::No_property, typename Base = internal_np::No_property>
struct Named_function_parameters : internal_np::Named_params_impl<T, Tag, Base> {
    typedef internal_np::Named_params_impl<T, Tag, Base> base;
    typedef Named_function_parameters<T, Tag, Base> self;
    
    Named_function_parameters() : base(T()) {}
    explicit Named_function_parameters(const T& v) : base(v) {}
    Named_function_parameters(const T& v, const Base& b) : base(v, b) {}
    
    // 用于SFINAE检测
    typedef int CGAL_Named_function_parameters_class;
};
 
namespace parameters {
 
// 默认参数
typedef Named_function_parameters<bool, internal_np::No_property> Default_named_parameters;
 
inline Default_named_parameters default_values() {
    return Default_named_parameters();
}
 
// 获取参数值
template <typename T, typename Tag, typename Base, typename QueryTag>
typename internal_np::Get_param<internal_np::Named_params_impl<T, Tag, Base>, QueryTag>::type
get_parameter(const Named_function_parameters<T, Tag, Base>& np, QueryTag) {
    return get_parameter_impl(static_cast<const internal_np::Named_params_impl<T, Tag, Base>>(np), QueryTag());
}
 
// 实现细节
template <typename T, typename Tag, typename Base>
const T& get_parameter_impl(const internal_np::Named_params_impl<T, Tag, Base>& np, Tag) {
    return np.v;
}
 
template <typename T, typename Tag, typename QueryTag>
internal_np::Param_not_found get_parameter_impl(const internal_np::Named_params_impl<T, Tag, internal_np::No_property>&, QueryTag) {
    return internal_np::Param_not_found();
}
 
template <typename T, typename Tag, typename Base, typename QueryTag>
auto get_parameter_impl(const internal_np::Named_params_impl<T, Tag, Base>& np, QueryTag tag)
    -> decltype(get_parameter_impl(static_cast<const Base>(np), tag)) {
    return get_parameter_impl(static_cast<const Base>(np), tag);
}
 
// 选择参数或默认值
template <typename D>
D& choose_parameter(const internal_np::Param_not_found&, D& d) { return d; }
 
template <typename D>
const D& choose_parameter(const internal_np::Param_not_found&, const D& d) { return d; }
 
template <typename T, typename D>
T& choose_parameter(T& t, D&) { return t; }
 
template <typename T, typename D>
const T& choose_parameter(const T& t, const D&) { return t; }
 
} // namespace parameters
 
// 参数标签定义
namespace internal_np {
 
#define CGAL_NAMED_PARAMETER(NAME, TAG) \
    struct TAG {}; \
    inline Named_function_parameters<bool, TAG> NAME() { \
        return Named_function_parameters<bool, TAG>(true); \
    } \
    template <typename T> \
    Named_function_parameters<T, TAG> NAME(const T& t) { \
        return Named_function_parameters<T, TAG>(t); \
    }
 
CGAL_NAMED_PARAMETER(vertex_point_map, vertex_point_map_tag)
CGAL_NAMED_PARAMETER(face_normal_map, face_normal_map_tag)
CGAL_NAMED_PARAMETER(number_of_iterations, number_of_iterations_tag)
CGAL_NAMED_PARAMETER(preserve_features, preserve_features_tag)
CGAL_NAMED_PARAMETER(edge_sensitivity, edge_sensitivity_tag)
CGAL_NAMED_PARAMETER(verbose, verbose_tag)
 
#undef CGAL_NAMED_PARAMETER
 
} // namespace internal_np
 
// 使用SFINAE检测命名参数
template<class T>
inline constexpr bool is_named_function_parameter = 
    std::is_same<typename std::decay<T>::type, 
                 Named_function_parameters<bool, internal_np::No_property>>::value;
 
} // namespace CGAL
 
// ============================================
// 使用示例
// ============================================
 
using namespace CGAL;
using namespace CGAL::parameters;
 
// 模拟网格处理函数
template <typename Mesh, typename NamedParameters = Default_named_parameters>
void process_mesh(Mesh& mesh, const NamedParameters& np = default_values()) {
    // 提取参数(带默认值)
    int num_iterations = choose_parameter(
        get_parameter(np, internal_np::number_of_iterations_tag()),
        3  // 默认值
    );
    
    bool preserve_features = choose_parameter(
        get_parameter(np, internal_np::preserve_features_tag()),
        false
    );
    
    double edge_sensitivity = choose_parameter(
        get_parameter(np, internal_np::edge_sensitivity_tag()),
        0.5
    );
    
    bool verbose = choose_parameter(
        get_parameter(np, internal_np::verbose_tag()),
        false
    );
    
    if (verbose) {
        std::cout << "Processing mesh with parameters:" << std::endl;
        std::cout << "  number_of_iterations: " << num_iterations << std::endl;
        std::cout << "  preserve_features: " << (preserve_features ? "true" : "false") << std::endl;
        std::cout << "  edge_sensitivity: " << edge_sensitivity << std::endl;
    }
    
    // 处理逻辑...
    std::cout << "Mesh processed successfully\!" << std::endl;
}
 
int main() {
    struct MockMesh {};
    MockMesh mesh;
    
    // 使用默认参数
    process_mesh(mesh);
    std::cout << "---" << std::endl;
    
    // 使用单个命名参数
    process_mesh(mesh, number_of_iterations(10));
    std::cout << "---" << std::endl;
    
    // 使用多个命名参数(任意顺序)
    process_mesh(mesh, 
                 preserve_features(true),
                 edge_sensitivity(0.8),
                 verbose(true));
    
    return 0;
}

19.4.4 CGAL中的应用

CGAL命名参数的实际使用

#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/remesh.h>
 
namespace PMP = CGAL::Polygon_mesh_processing;
 
// 使用命名参数调用网格重划分
void remesh_example(Surface_mesh<Point_3>& mesh) {
    // 清晰的参数指定
    PMP::isotropic_remeshing(
        faces(mesh),
        0.5,  // target_edge_length
        mesh,
        CGAL::parameters::number_of_iterations(3)
                        .protect_constraints(true)
                        .smooth_along_features(true)
    );
}

命名参数与BGL集成

// CGAL的BGL(Boost Graph Library)扩展使用命名参数
#include <CGAL/boost/graph/named_params.h>
 
// 访问顶点位置属性
template <typename PolygonMesh, typename NamedParameters>
void compute_normals(const PolygonMesh& pmesh, const NamedParameters& np) {
    // 使用vertex_point_map参数获取顶点位置
    typedef typename boost::lookup_named_param_def<
        CGAL::vertex_point_t,
        NamedParameters,
        typename boost::property_map<PolygonMesh, CGAL::vertex_point_t>::const_type
    >::type VertexPointMap;
    
    VertexPointMap vpm = boost::choose_param(
        boost::get_param(np, CGAL::vertex_point_t()),
        get(CGAL::vertex_point, pmesh)
    );
    
    // 使用vpm访问顶点坐标...
}

19.4.5 常见陷阱

陷阱1:参数顺序依赖

// 错误:某些实现可能有参数顺序问题
auto params = param1(value1), param2(value2);  // 可能有问题
 
// 正确:使用链式调用(如果支持)
auto params = param1(value1).param2(value2);

陷阱2:类型推导问题

// 可能的问题:字符串字面量推导为const char*而非std::string
process_mesh(mesh, file_name("output.mesh"));  // 可能不匹配
 
// 解决方案:显式指定类型或使用辅助函数
process_mesh(mesh, file_name(std::string("output.mesh")));

陷阱3:引用生命周期

// 危险:引用可能悬空
auto params = vertex_point_map(create_temporary_map());
// create_temporary_map()返回的临时对象在语句结束后销毁
 
// 解决方案:确保参数生命周期足够长
auto map = create_map();
auto params = vertex_point_map(map);

陷阱4:与函数重载混淆

// 问题:编译器可能无法区分命名参数和函数重载
void foo(int x);
void foo(const NamedParams& np);
 
foo(42);  // 调用foo(int)
foo(param = 42);  // 调用foo(NamedParams)

19.4.6 最佳实践

实践1:提供合理的默认值

// 每个参数都应该有合理的默认值
template <typename NamedParameters>
void algorithm(const NamedParameters& np = parameters::default_values()) {
    int iterations = choose_parameter(
        get_parameter(np, number_of_iterations_tag()),
        10  // 合理的默认值
    );
    
    double threshold = choose_parameter(
        get_parameter(np, threshold_tag()),
        0.001  // 合理的默认值
    );
}

实践2:文档化参数含义

/**
 * @brief 网格平滑处理
 * @param mesh 输入网格
 * @param np 可选命名参数:
 *   - number_of_iterations: 迭代次数(默认:1)
 *   - relax_geometry: 是否松弛几何(默认:true)
 *   - area_threshold: 面积阈值(默认:0.0)
 */
template <typename Mesh, typename NamedParameters>
void smooth_mesh(Mesh& mesh, const NamedParameters& np);

实践3:参数验证

template <typename NamedParameters>
void algorithm(const NamedParameters& np) {
    int iterations = choose_parameter(
        get_parameter(np, number_of_iterations_tag()), 10
    );
    
    // 验证参数有效性
    CGAL_precondition(iterations > 0);
    CGAL_precondition(iterations < 1000);
}

实践4:向后兼容性

// 添加新参数时保持向后兼容
// 旧代码
process_mesh(mesh, target_edge_length(0.5));
 
// 新代码(添加新参数,不影响旧代码)
process_mesh(mesh, 
             target_edge_length(0.5),
             new_feature(true));  // 新参数有默认值

本节要点

  1. 命名参数提高代码可读性:特别是在参数众多、大部分有默认值的情况下。

  2. 实现原理:通过递归模板参数包和标签类型实现,利用编译期多态。

  3. 与默认参数的区别:命名参数顺序无关,可以轻松跳过某些参数。

  4. CGAL的广泛应用:网格处理、点云处理等模块大量使用命名参数模式。

  5. 注意事项:注意参数生命周期、类型推导和向后兼容性。

  6. 最佳实践:提供合理默认值、文档化参数、验证参数有效性。


进一步阅读

  • CGAL文档:Polygon Mesh Processing模块的Named Parameters章节
  • Boost.Parameter库:命名参数的经典实现
  • Fluent Interface模式:与命名参数相关的API设计模式