摘要 | 使用此库编写可按名称接受参数的函数和类模板 |
---|
new_window( "alert" , _width=10 , _titlebar=false ); smart_ptr< Foo , deleter<Deallocate<Foo> > , copy_policy<DeepCopy> > p(new Foo);
由于命名参数可以按任意顺序传递,因此当函数或模板具有多个带有有用默认值的参数时,它们特别有用。该库还支持*推导*参数:也就是说,可以根据其类型推导出其身份的参数。
作者 | David Abrahams, Daniel Wallin |
---|---|
联系方式 | dave@boost-consulting.com, daniel@boostpro.com |
organization | BoostPro Computing |
版权 | 版权所有 David Abrahams, Daniel Wallin 2005-2009。根据 Boost 软件许可证 1.0 版分发。(参见随附文件 LICENSE_1_0.txt 或在 https://boost.ac.cn/LICENSE_1_0.txt 复制) |
[注意:本教程不涵盖库的所有详细信息。另请参阅参考文档]
在 C++ 中,实参通常通过其相对于形参列表的位置来赋予意义:传递的第一个实参映射到函数定义中的第一个形参,依此类推。当最多只有一个带有默认值的形参时,该协议很好,但当有几个有用的默认值时,位置接口就会变得繁琐
由于实参的含义由其位置决定,我们必须为带有默认值的形参选择一个(通常是任意的)顺序,这使得某些默认值的组合无法使用
window* new_window( char const* name , int border_width = default_border_width , bool movable = true , bool initially_visible = true ); bool const movability = false; window* w = new_window("alert box", movability);
在上面的示例中,我们想要创建一个不可移动的窗口,其默认border_width为零,但我们得到的是一个可移动的窗口,其border_width为零。要获得所需的效果,我们需要编写
window* w = new_window( "alert box", default_border_width, movability );
读者很难理解调用站点的实参含义
window* w = new_window("alert", 1, true, false);
这个窗口是可移动且最初不可见的,还是不可移动且最初可见的?读者需要记住实参的顺序才能确定。
调用的作者也可能不记得实参的顺序,从而导致难以找到的错误。
该库通过将每个形参名称与一个关键字对象相关联来解决上述问题。现在用户可以通过名称而不是位置来识别实参
window* w = new_window( "alert box" , movable_=false ); // OK!
一个**推导参数**可以以任何位置传递,*无需*提供显式参数名称。函数具有基于传递参数类型可以唯一标识的参数并不少见。名称参数到new_window就是一个这样的例子。如果没有其他有效参数,它们都无法合理地转换为char const*。使用推导参数接口,我们可以在*任何*参数位置传递窗口名称,而不会造成歧义
window* w = new_window( movable_=false , "alert box" ); // OK! window* w = new_window( "alert box" , movable_=false ); // OK!
适当使用推导参数接口可以减轻用户记住形式参数名称的负担。
我们对命名参数和推导参数接口的推理同样适用于类模板和函数。使用参数库,我们可以创建允许模板参数(在本例中为sharedand客户端)显式命名的接口,如下所示
smart_ptr< ownership<shared> , value_type<Client> > p;
传递命名模板实参的语法不如函数实参自然(理想情况下,我们能够编写smart_ptr<ownership = shared, …>)。这种小的语法缺陷使得推导参数在与类模板一起使用时尤其大有裨益
// p and q could be equivalent, given a deduced // parameter interface. smart_ptr<shared, Client> p; smart_ptr<Client, shared> q;
本教程将展示所有基础知识——如何为函数模板和类模板构建命名参数和推导参数接口——以及一些更高级的用法。
在本节中,我们将展示如何使用参数库为 Boost Graph 库的 depth_first_search 算法构建一个富有表现力的接口。1
参数库的大多数组件都在以组件命名的头文件中声明。例如,
#include <boost/parameter/keyword.hpp>
将确保boost::parameter::keyword被编译器识别。还有一个组合头文件,boost/parameter.hpp,它包含了库的大部分组件。在本教程的其余部分,除非另有说明,否则您可以使用上述规则来确定要#include访问库的任何给定组件。
此外,以下示例也将编写为已声明命名空间别名
namespace parameter = boost::parameter;
:我们将编写parameter::xxx而不是boost::parameter::xxx.
图库的depth_first_search算法是一个通用函数,通过引用接受一到四个参数。如果所有参数都是必需的,其签名可能如下
template < typename Graph , typename DFSVisitor , typename Index , typename ColorMap > void depth_first_search( Graph const& graph , DFSVisitor visitor , typename graph_traits<g>::vertex_descriptor root_vertex , IndexMap index_map , ColorMap& color );
然而,大多数参数都有一个有用的默认值,如下表所示。
参数名称 | 数据流 | 类型 | 默认值(如果有) |
---|---|---|---|
graph | in | incidence_graph 和 vertex_list_graph 模型 | 无 - 此参数是必需的。 |
visitor | in | DFSVisitor 模型 | boost::dfs_visitor<>() |
root_vertex | in | graph的顶点描述符类型。 | *vertices(graph).first |
distance_compare(CompareFunction distance_compare) | in | Readable Property Map 模型,键类型 :=graph的顶点描述符,值类型为整数类型。 | get(boost::vertex_index,graph) |
color_map | 输入 / 输出 | Read/Write Property Map 模型,键类型 :=graph的顶点描述符类型。 | aboost::iterator_property_map,由一个std::vector的default_color_type大小为index_map并使用distance_compare(CompareFunction distance_compare)此函数用于比较距离,以确定哪个顶点更靠近源顶点。该 |
不要被上面第二列和第三列的信息吓倒。为了本次练习的目的,您不需要详细了解它们。
本练习的目的是使调用depth_first_search成为可能,并带有命名参数,省略所有适用默认值的参数
graphs::depth_first_search(g, color_map_=my_color_map);
为了使该语法合法,需要有一个名为“color_map_”的对象,其赋值运算符可以接受my_color_map参数。在此步骤中,我们将为每个参数创建一个这样的**关键字对象**。每个关键字对象将由一个唯一的**关键字标签类型**标识。
我们将在命名空间graphs中定义我们的接口。该库提供了一个方便的宏来定义关键字对象
#include <boost/parameter/name.hpp> namespace graphs { BOOST_PARAMETER_NAME(graph) // Note: no semicolon BOOST_PARAMETER_NAME(visitor) BOOST_PARAMETER_NAME(root_vertex) BOOST_PARAMETER_NAME(index_map) BOOST_PARAMETER_NAME(color_map) }
您在此处看到的graph关键字声明等效于
namespace graphs { namespace tag { // keyword tag type struct graph { typedef boost::parameter::forward_reference qualifier; }; } namespace // unnamed { // A reference to the keyword object boost::parameter::keyword<tag::graph> const& _graph = boost::parameter::keyword<tag::graph>::instance; } }
它定义了一个名为tag::graph的*关键字标签类型*和一个名为_graph.
的*关键字对象*引用。这种涉及未命名命名空间和引用的“花哨操作”是为了避免在命名参数接口被实例化在多个翻译单元中的函数模板使用时违反一次定义规则(ODR)2(MSVC6.x 用户请参阅此说明)。
现在我们已经定义了关键字,函数模板定义遵循一个使用forward宏
#include <boost/parameter/preprocessor.hpp> namespace graphs { BOOST_PARAMETER_FUNCTION( (void), // 1. parenthesized return type depth_first_search, // 2. name of the function template tag, // 3. namespace of tag types (required (graph, *) ) // 4. one required parameter, and (optional // four optional parameters, // with defaults (visitor, *, boost::dfs_visitor<>()) (root_vertex, *, *vertices(graph).first) (index_map, *, get(boost::vertex_index,graph)) (color_map, *, default_color_map(num_vertices(graph), index_map) ) ) ) { // ... body of function goes here... // use graph, visitor, index_map, and color_map } }
的简单模式。forward公亩
函数签名被描述为一个或两个相邻的带括号项(Boost.Preprocessor 序列),描述函数参数的顺序,如果按位置传递,它们将按此顺序排列。任何必需参数必须首先出现,但当所有参数都是可选时,可以省略(required … )子句。
必选参数首先给出——嵌套在(required … )子句中——作为一系列描述每个参数名称和任何参数类型要求的两元素元组。在这种情况下只有一个必选参数,所以只有一个元组
(required (graph, *) )
由于depth_first_search不需要其graph参数的任何特定类型,我们使用星号表示允许任何类型。必选参数必须始终位于签名中的任何可选参数之前,但如果*没有*必选参数,则可以完全省略(required … )子句。
可选参数——嵌套在(optional … )子句中——作为一系列相邻的*三*元素元组给出,描述参数名称、参数类型的任何要求,*以及*表示参数默认值的表达式
(optional (visitor, *, boost::dfs_visitor<>()) (root_vertex, *, *vertices(graph).first) (index_map, *, get(boost::vertex_index,graph)) (color_map, *, default_color_map(num_vertices(graph), index_map) ) )
默认情况下,Boost.Parameter 将所有参数视为*转发*参数,函数将通过右值引用接收它们,并且只将它们*移动*到其他函数。此类参数可以是(deduced …)或boost::forward左值、可变左值、const右值或可变右值。因此,默认配置为用户代码提供了最大的灵活性。但是const用户可以将一个或多个参数配置为*输入*参数,这些参数可以属于与*转发*参数相同的类别,但现在通过
BOOST_PARAMETER_NAME(graph) BOOST_PARAMETER_NAME(visitor) BOOST_PARAMETER_NAME(in(root_vertex)) BOOST_PARAMETER_NAME(in(index_map)) BOOST_PARAMETER_NAME(in_out(color_map))
为了查看当参数绑定到违反其类别约束的实参时会发生什么,请尝试使用LIBS_PARAMETER_TEST_COMPILE_FAILURE_0宏或LIBS_PARAMETER_TEST_COMPILE_FAILURE_1宏如果 BOOST_PARAMETER_HAS_PERFECT_FORWARDING 已编译compose.cpp测试程序。您应该会遇到由特定约束违反引起的编译器错误。
当参数按位置传递(不使用关键字)时,它们将按照签名中参数给出的顺序映射到参数,例如在此调用中
graphs::depth_first_search(x, y);
x将始终被解释为图,而y将始终被解释为访问者。
请注意,在我们的示例中,图参数的值用于root_vertex, distance_compare(CompareFunction distance_compare)和color_map.
(required (graph, *) ) (optional (visitor, *, boost::dfs_visitor<>()) (root_vertex, *, *vertices(graph).first) (index_map, *, get(boost::vertex_index, graph)) (color_map, *, default_color_map(num_vertices(graph), index_map) ) )
的默认表达式中。depth_first_search如果为该参数传递了实际参数,则默认表达式永远不会被评估(甚至实例化)。我们可以通过将
#include <boost/graph/depth_first_search.hpp> // for dfs_visitor BOOST_PARAMETER_FUNCTION( (bool), depth_first_search, tag …signature goes here… ) { std::cout << "graph=" << graph; std::cout << std::endl; std::cout << "visitor=" << visitor; std::cout << std::endl; std::cout << "root_vertex=" << root_vertex; std::cout << std::endl; std::cout << "index_map=" << index_map; std::cout << std::endl; std::cout << "color_map=" << color_map; std::cout << std::endl; return true; } #include <boost/core/lightweight_test.hpp> int main() { char const* g = "1"; depth_first_search(1, 2, 3, 4, 5); depth_first_search( g, '2', _color_map = '5', _index_map = "4", _root_vertex = "3" ); return boost::report_errors(); }
的主体替换为打印参数的内容来演示这一点尽管诸如vertices(graph).firstgraph之类的默认表达式对于给定的
2.1.5.6 签名匹配和重载depth_first_search事实上,函数签名是如此通用,以至于任何对graph的调用,如果参数少于五个,都将匹配我们的函数,前提是我们为必需的depth_first_search参数传递了*某些东西*。这起初可能看起来不是问题;毕竟,如果参数与
的实现所施加的要求不匹配,则在其实例化时会发生编译错误。
隔离在一个不包含类型的命名空间中6,但假设我们*希望*它通过 ADL 找到呢?graph当传递的实参类型不合适时,阻止函数被考虑用于重载解析通常是一个好主意。当未提供所需的
// new overload BOOST_PARAMETER_FUNCTION((void), depth_first_search, (tag), (required (graph,*))( … ) ) { // new algorithm implementation } … // ambiguous! depth_first_search(boost::adjacency_list<>(), 2, "hello");
2.1.5.6.1 谓词要求depth_first_search我们真的不希望编译器考虑原始版本的root_vertex,因为参数,"hello"graph,不符合它必须匹配depth_first_search参数的顶点描述符类型的要求。相反,此调用应该只调用我们的新重载。为了使原始的
struct vertex_descriptor_predicate { template <typename T, typename Args> struct apply : boost::mpl::if_< boost::is_convertible< T , typename boost::graph_traits< typename boost::parameter::value_type< Args , graphs::graph >::type >::vertex_descriptor > , boost::mpl::true_ , boost::mpl::false_ > { }; };
重载退出竞争,我们首先将此要求编码如下apply这种编码是一个 MPL 二进制元函数类,一个带有嵌套的类型depth_first_search元函数,它接受两个模板参数。对于第一个模板参数,Boost.Parameter 将传入我们将施加要求的类型。对于第二个模板参数,Boost.Parameter 将传入整个参数包,从而可以通过value_type元函数和相应的关键字标签类型提取其他每个type创建,其大小为apply参数的类型。结果元函数将等效于ifTboost::mpl::true_满足我们的要求,由boost::is_convertible语句施加;否则,结果将等效于.
boost::mpl::false_*此时,我们可以将元函数类的名称(用括号括起来)附加到函数签名中相应的
(root_vertex , *(vertex_descriptor_predicate) , *vertices(graph).first )
元素后面。depth_first_search现在,只有当root_vertex参数可以转换为图的顶点描述符类型时,原始的
才会被调用,而我们之前模棱两可的示例将顺利地调用新的重载。apply我们可以使用相同的概念对其他参数进行要求编码;只需调整嵌套的
struct graph_predicate { template <typename T, typename Args> struct apply : boost::mpl::eval_if< boost::is_convertible< typename boost::graph_traits<T>::traversal_category , boost::incidence_graph_tag > , boost::mpl::if_< boost::is_convertible< typename boost::graph_traits<T>::traversal_category , boost::vertex_list_graph_tag > , boost::mpl::true_ , boost::mpl::false_ > > { }; }; struct index_map_predicate { template <typename T, typename Args> struct apply : boost::mpl::eval_if< boost::is_integral< typename boost::property_traits<T>::value_type > , boost::mpl::if_< boost::is_same< typename boost::property_traits<T>::key_type , typename boost::graph_traits< typename boost::parameter::value_type< Args , graphs::graph >::type >::vertex_descriptor > , boost::mpl::true_ , boost::mpl::false_ > > { }; }; struct color_map_predicate { template <typename T, typename Args> struct apply : boost::mpl::if_< boost::is_same< typename boost::property_traits<T>::key_type , typename boost::graph_traits< typename boost::parameter::value_type< Args , graphs::graph >::type >::vertex_descriptor > , boost::mpl::true_ , boost::mpl::false_ > { }; };
元函数的实现以适应每个参数。这里没有空间详细描述图库的细节,但足以说明接下来的几个元函数类提供了必要的检查。color_map同样,计算
template <typename Size, typename IndexMap> boost::iterator_property_map< std::vector<boost::default_color_type>::iterator , IndexMap , boost::default_color_type , boost::default_color_type& >& default_color_map(Size num_vertices, IndexMap const& index_map) { static std::vector<boost::default_color_type> colors(num_vertices); static boost::iterator_property_map< std::vector<boost::default_color_type>::iterator , IndexMap , boost::default_color_type , boost::default_color_type& > m(colors.begin(), index_map); return m; }
参数的默认值并非易事,因此最好将计算分解为单独的函数模板。
BOOST_PARAMETER_FUNCTION((void), depth_first_search, graphs, (required (graph, *(graph_predicate)) ) (optional (visitor , * // not easily checkable , boost::dfs_visitor<>() ) (root_vertex , (vertex_descriptor_predicate) , *vertices(graph).first ) (index_map , *(index_map_predicate) , get(boost::vertex_index, graph) ) (color_map , *(color_map_predicate) , default_color_map(num_vertices(graph), index_map) ) ) )
签名将每个谓词元函数括在括号中,并*前面带一个星号*,如下所示
2.1.5.6.2 更多关于类型要求new_window将类型要求编码到函数的参数上对于启用函数的推导参数接口至关重要。让我们再回顾一下
window* w = new_window( movable_=false , "alert box" ); window* w = new_window( "alert box" , movable_=false );
示例new_window这次的目标是能够不指定关键字就调用*函数。对于每个具有所需类型的参数,我们可以将该类型括在括号中,然后*替换*参数签名的
BOOST_PARAMETER_NAME((name_, keywords) name) BOOST_PARAMETER_NAME((movable_, keywords) movable) BOOST_PARAMETER_FUNCTION((window*), new_window, keywords, (deduced (required (name, (char const*)) (movable, (bool)) ) ) ) { // ... }
元素
window* w = new_window(false, "alert box"); window* w = new_window("alert box", false);
2.1.5.7 推导参数
template < typename Function , typename KeywordExpression , typename CallPolicies > void def( // Required parameters char const* name, Function func // Optional, deduced parameters , char const* docstring = "" , KeywordExpression keywords = no_keywords() , CallPolicies policies = default_call_policies() );
为了进一步说明推导参数的支持,请考虑 Boost.Python 中 def 函数的示例。其签名大致如下
尽量不要被这个例子中“keywords”一词的使用分散注意力:尽管它在 Boost.Python 中与参数库中的含义相似,但为了本次练习的目的,您可以将其视为完全不同的东西。def调用forward时,只有两个参数是必需的。任何额外参数及其参数之间的关联可以通过实际传递的参数类型来确定,因此调用者无需记住参数位置或显式指定这些参数的参数名称。要使用的方括号运算符,以提取与相应命名参数(无下划线)绑定的相同参数,如下文所示。生成此接口,我们只需将推导参数括在
char const*& blank_char_ptr() { static char const* larr = ""; return larr; } BOOST_PARAMETER_FUNCTION( (bool), def, tag, (required (name, (char const*)) (func,*) ) // nondeduced (deduced (optional (docstring, (char const*), "") (keywords // see5 , *(is_keyword_expression<boost::mpl::_>) , no_keywords() ) (policies , *( boost::mpl::eval_if< boost::is_convertible<boost::mpl::_,char const*> , boost::mpl::false_ , boost::mpl::if_< // see5 is_keyword_expression<boost::mpl::_> , boost::mpl::false_ , boost::mpl::true_ > > ) , default_call_policies() ) ) ) ) { … }
子句中,如下所示
A的方括号运算符,以提取与相应命名参数(无下划线)绑定的相同参数,如下文所示。语法注意事项子句始终包含一个(required …)和/或一个(optional …)子句始终包含一个或和/或一个子句,并且必须跟在外部层级表示非推导参数的任何
子句之后。
char const* f_name = "f"; def( f_name , &f , some_policies , "Documentation for f" ); def( f_name , &f , "Documentation for f" , some_policies );
有了上面的声明,以下两个调用是等效的policies如果用户想要传递一个char const*参数,由于某种原因,它也可以转换为
def( f_name , &f , _policies = some_policies , "Documentation for f" );
,她总是可以明确指定参数名称,如下所示
2.1.5.8 参数相关的返回类型forward对于某些算法,返回类型取决于至少一个参数类型。然而,在使用
BOOST_PARAMETER_NAME(x) BOOST_PARAMETER_NAME(y) BOOST_PARAMETER_NAME(z)
或其他代码生成宏时,在编码此返回类型时需要避免一个陷阱。例如,给定以下定义假设我们的算法简单地返回其参数之一。如果我们天真地根据:
BOOST_PARAMETER_FUNCTION( (typename parameter::value_type<Args,tag::y>::type), return_y, tag, (deduced (required (x, (std::map<char const*,std::string>)) (y, (char const*)) ) (optional (z, (int), 4) ) ) ) { return y; }
parameter::value_typereturn_y实现其返回类型
std::map<char const*,std::string> k2s; assert("foo" == return_y(2, k2s, "foo")); // error!
那么以任何方式使用y(除了位置参数)都将导致编译器错误forward问题在于,尽管Args是一个必需参数,但会生成某些重载,其中参数包类型实际上不包含关键字标签类型假设我们的算法简单地返回其参数之一。如果我们天真地根据tag::y。解决方案是使用 SFINAE 排除这些重载的生成。由于:
BOOST_PARAMETER_FUNCTION( ( // Whenever using 'enable_if', 'disable_if', and so on, // do not add the 'typename' keyword in front. boost::lazy_enable_if< typename mpl::has_key<Args,tag::y>::type , parameter::value_type<Args,tag::y> > // Whenever using 'enable_if', 'disable_if', and so on, // do not add '::type' here. ), return_y, tag, (deduced (required (x, (std::map<char const*,std::string>)) (y, (char const*)) ) (optional (z, (int), 4) ) ) ) { return y; }
是一个元函数,我们的工具是
该lazy_enable_ifand。有关工作演示,请参阅preprocessor_deduced.cpp。BOOST_PARAMETER_MEMBER_FUNCTIONforward和
BOOST_PARAMETER_NAME(arg1) BOOST_PARAMETER_NAME(arg2) struct callable2 { BOOST_PARAMETER_CONST_MEMBER_FUNCTION( (void), call, tag, (required (arg1,(int))(arg2,(int))) ) { std::cout << arg1 << ", " << arg2; std::cout << std::endl; } }; #include <boost/core/lightweight_test.hpp> int main() { callable2 c2; callable2 const& c2_const = c2; c2_const.call(1, 2); return boost::report_errors(); }
BOOST_PARAMETER_CONST_MEMBER_FUNCTION
struct callable2 { BOOST_PARAMETER_CONST_MEMBER_FUNCTION( (void), call, tag, (required (arg1,(int))(arg2,(int))) ) { call_impl(arg1, arg2); } private: void call_impl(int, int); // implemented elsewhere. };
完全相同的参数,但旨在用于类的主体中静态这些宏不直接允许函数的接口与其实现分离,但您始终可以将参数转发到单独的实现函数
BOOST_PARAMETER_NAME(arg1) struct somebody { BOOST_PARAMETER_MEMBER_FUNCTION( (void), static f, tag, (optional (arg1,(int),0)) ) { std::cout << arg1 << std::endl; } }; #include <boost/core/lightweight_test.hpp> int main() { somebody::f(); somebody::f(4); return boost::report_errors(); }
该2.2.1 静态成员函数and要暴露静态成员函数,只需在函数名称前插入关键字“”即可lazy_enable_ifand。有关工作演示,请参阅preprocessor_deduced.cpp。BOOST_PARAMETER_FUNCTION_CALL_OPERATOR
BOOST_PARAMETER_NAME(first_arg) BOOST_PARAMETER_NAME(second_arg) struct callable2 { BOOST_PARAMETER_CONST_FUNCTION_CALL_OPERATOR( (void), tag, (required (first_arg,(int))(second_arg,(int))) ) { std::cout << first_arg << ", "; std::cout << second_arg << std::endl; } }; #include <boost/core/lightweight_test.hpp> int main() { callable2 c2; callable2 const& c2_const = c2; c2_const(1, 2); return boost::report_errors(); }
和
BOOST_PARAMETER_CONST_FUNCTION_CALL_OPERATOR
BOOST_PARAMETER_NAME(name) BOOST_PARAMETER_NAME(index) struct myclass_impl { template <typename ArgumentPack> myclass_impl(ArgumentPack const& args) { std::cout << "name = " << args[_name]; std::cout << "; index = " << args[_index | 42]; std::cout << std::endl; } };
宏接受与|宏相同的参数,但函数名称除外,因为这些宏允许将包含类的实例视为函数对象索引C++中缺少“委托构造函数”功能(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1986.pdf)在一定程度上限制了该库为定义启用参数的构造函数所能提供的接口质量。缺少构造函数委托的常见解决方法是:必须将通用逻辑分解到一个或多个基类中。42让我们构建一个启用参数的构造函数,它简单地打印其参数。第一步是编写一个基类,其构造函数接受一个称为ArgumentPack的单个参数:实际参数引用的捆绑包,并用它们的关键字进行标记。实际参数的值通过使用关键字对象对其进行*索引*从ArgumentPack中提取
请注意,当位或(“
struct myclass : myclass_impl { BOOST_PARAMETER_CONSTRUCTOR( myclass, (myclass_impl), tag , (required (name,*)) (optional (index,*)) ) // no semicolon };
”)运算符应用于传递给ArgumentPack索引运算符的关键字对象时,它具有特殊的含义:它用于指示默认值。在这种情况下,如果ArgumentPack中没有索引参数,则将使用名称代替。名称现在我们准备编写启用参数的构造函数接口
myclass x("bob", 3); // positional myclass y(_index = 12, _name = "sally"); // named myclass z("june"); // positional/defaulted
由于我们为
提供了默认值,但没有为
template < ValueType, BaseList = bases<> , HeldType = ValueType, Copyable = void > class class_;
提供,因此只有ValueType是必需的。我们可以按以下方式使用我们的新接口
在本节中,我们将使用 Boost.Parameter 构建 Boost.Python 的 class_ 模板,其“签名”为
struct B { virtual ~B() = 0; }; struct D : B { ~D(); }; class_< class_type<B> , copyable<boost::noncopyable> > …; class_< D , held_type<std::auto_ptr<D> > , base_list<bases<B> > > …;
,是必需的。
namespace boost { namespace python { BOOST_PARAMETER_TEMPLATE_KEYWORD(class_type) BOOST_PARAMETER_TEMPLATE_KEYWORD(base_list) BOOST_PARAMETER_TEMPLATE_KEYWORD(held_type) BOOST_PARAMETER_TEMPLATE_KEYWORD(copyable) }}
您在此处看到的2.5.1 命名模板参数关键字声明等效于
namespace boost { namespace python { namespace tag { struct class_type; // keyword tag type } template <typename T> struct class_type : parameter::template_keyword<tag::class_type,T> { }; }}
首先,我们将构建一个允许用户按位置或按名称传递参数的接口2.5.1.1 模板关键字第一步是为每个模板参数定义关键字2.5.1 命名模板参数.
它定义了一个名为tag::class_type的关键字标签类型和一个名为
namespace boost { namespace python { template < typename A0 , typename A1 = boost::parameter::void_ , typename A2 = boost::parameter::void_ , typename A3 = boost::parameter::void_ > struct class_ { … }; }}
2.5.1.2 类模板骨架下一步是定义我们的类模板骨架,它有三个可选参数。由于用户可以按任意顺序传递参数,我们不知道这些参数的实际身份,因此使用描述性名称或为其中任何一个编写实际默认值都为时过早。相反,我们将给它们通用名称并使用特殊类型boost::parameter::void_
namespace boost { namespace python { using boost::mpl::_; typedef parameter::parameters< required<tag::class_type, boost::is_class<_> > , parameter::optional<tag::base_list, mpl::is_sequence<_> > , parameter::optional<tag::held_type> , parameter::optional<tag::copyable> > class_signature; }}
2.5.1.3 类模板签名接下来,我们需要构建一个类型,称为ParameterSpec,描述boost::python::class_的“签名”。ParameterSpec 以其位置顺序枚举必需和可选参数,以及任何类型要求(请注意,它*不*指定默认值——这些将单独处理)2.5.1.4 参数包和参数提取接下来,在class_接下来,在的主体中,我们使用ParameterSpec的嵌套::bind< … >模板将实际参数捆绑到ArgumentPack类型中,然后使用库的
namespace boost { namespace python { template < typename A0 , typename A1 = boost::parameter::void_ , typename A2 = boost::parameter::void_ , typename A3 = boost::parameter::void_ > struct class_ { // Create ArgumentPack typedef typename class_signature::template bind< A0, A1, A2, A3 >::type args; // Extract first logical parameter. typedef typename parameter::value_type< args, tag::class_type >::type class_type; typedef typename parameter::value_type< args, tag::base_list, bases<> >::type base_list; typedef typename parameter::value_type< args, tag::held_type, class_type >::type held_type; typedef typename parameter::value_type< args, tag::copyable, void >::type copyable; }; }}
元函数提取“逻辑参数”。
typedef boost::python::class_< class_type<B>, copyable<boost::noncopyable> > c1; typedef boost::python::class_< D , held_type<std::auto_ptr<D> > , base_list<bases<B> > > c2;
与
BOOST_MPL_ASSERT((boost::is_same<c1::class_type, B>)); BOOST_MPL_ASSERT((boost::is_same<c1::base_list, bases<> >)); BOOST_MPL_ASSERT((boost::is_same<c1::held_type, B>)); BOOST_MPL_ASSERT(( boost::is_same<c1::copyable, boost::noncopyable> )); BOOST_MPL_ASSERT((boost::is_same<c2::class_type, D>)); BOOST_MPL_ASSERT((boost::is_same<c2::base_list, bases<B> >)); BOOST_MPL_ASSERT(( boost::is_same<c2::held_type, std::auto_ptr<D> > )); BOOST_MPL_ASSERT((boost::is_same<c2::copyable, void>));
非常相似,但不会向实际参数类型添加引用。请注意,默认值通过传递可选的第三个参数来指定2.5.2 测试至今的代码and回顾我们最初的例子,我们现在可以检查预期的参数2.5.3 推导模板参数要在此处应用推导参数接口,我们只需要使类型要求更严格一些,以便held_type和回顾我们最初的例子,copyableboost::noncopyable参数可以与其他参数清晰地区分。Boost.Python 通过要求held_typebase_list
namespace boost { namespace python { namespace detail { struct bases_base { }; } template < typename A0 = void, typename A1 = void, typename A2 = void … > struct bases : detail::bases_base { }; }}
是其
typedef parameter::parameters< required<tag::class_type, is_class<_> > , parameter::optional< deduced<tag::base_list> , is_base_and_derived<detail::bases_base,_> > , parameter::optional< deduced<tag::held_type> , mpl::not_< mpl::or_< is_base_and_derived<detail::bases_base,_> , is_same<noncopyable,_> > > > , parameter::optional< deduced<tag::copyable> , is_same<noncopyable,_> > > class_signature;
bases< … >
typedef boost::python::class_<B, boost::noncopyable> c1; typedef boost::python::class_< D, std::auto_ptr<D>, bases<B> > c2;
模板的特化(而不是任何旧的 MPL 序列)并且要求
(如果显式提供)是标签。识别,元数范围从:
BOOST_PARAMETER_NAME( ( object-name , tag-namespace ) parameter-name )
特化的一种简单方法是将它们都派生自同一个类,作为实现细节
BOOST_PARAMETER_NAME( ( pass_foo, keywords ) foo ) BOOST_PARAMETER_FUNCTION( (int), f, keywords, (required (foo, *)) ) { return foo + 1; } int x = f(pass_foo = 41);
现在我们可以重写签名,使所有三个可选参数都是可推导的
这似乎增加了大量的复杂性,但对用户的好处更大。我们最初的示例现在可以在不显式指定参数名称的情况下编写
如果您不喜欢用于引用关键字对象的下划线前缀命名约定,或者您需要将名称
BOOST_PARAMETER_NAME(index) template <typename ArgumentPack> int print_index(ArgumentPack const& args) { std::cout << "index = " << args[_index]; std::cout << std::endl; return 0; } int x = print_index(_index = 3); // prints "index = 3"
用于关键字类型命名空间以外的其他用途,则还有另一种使用的方法:
BOOST_PARAMETER_NAME(name) template <typename ArgumentPack> int print_name_and_index(ArgumentPack const& args) { std::cout << "name = " << args[_name]; std::cout << "; "; return print_index(args); } int y = print_name_and_index((_index = 3, _name = "jones"));
这是一个使用示例
但是,在您使用这种更详细的形式之前,请阅读有关关键字对象命名最佳实践的部分。的“签名”。ParameterSpec 以其位置顺序枚举必需和可选参数,以及任何类型要求(请注意,它*不*指定默认值——这些将单独处理)我们已经在查看启用参数的构造函数和类模板时看到了ArgumentPack。您可能已经猜到,ArgumentPack实际上是该库所有功能的核心;在本节中,我们将研究如何更有效地构建和操作它们。
parameter::parameters< required<tag::name, is_convertible<_,char const*> > , optional<tag::index, is_convertible<_,int> > > spec; char const sam[] = "sam"; int twelve = 12; int z0 = print_name_and_index( spec( sam, twelve ) ); int z1 = print_name_and_index( spec( _index=12, _name="sam" ) );
最简单的 ArgumentPack 是赋值给关键字对象的结果的方法此外,ArgumentPacks 可以使用逗号运算符组合。下面的额外括号用于防止编译器将两个单独的参数视为
BOOST_PARAMETER_NAME(name) BOOST_PARAMETER_NAME(index) template <typename Name, typename Index> int deduce_arg_types_impl(Name&& name, Index&& index) { // we know the types Name&& n2 = boost::forward<Name>(name); Index&& i2 = boost::forward<Index>(index); return index; } template <typename ArgumentPack> int deduce_arg_types(ArgumentPack const& args) { return deduce_arg_types_impl(args[_name], args[_index | 42]); }
print_name_and_index索引。compose.cpp 测试程序显示了此功能的更多示例。接下来,在为了构建带有位置参数的 ArgumentPack,我们可以使用 ParameterSpec。正如在类模板签名部分中所述,ParameterSpec 描述了参数的位置顺序和任何相关的类型要求。正如我们可以使用其嵌套的
BOOST_PARAMETER_NAME(index) template <typename ArgumentPack> typename boost::parameter::value_type<ArgumentPack,tag::index,int>::type twice_index(ArgumentPack const& args) { return 2 * args[_index | 42]; }
模板构建 ArgumentPack *类型*一样,我们可以通过调用其函数调用运算符构建 ArgumentPack *对象*::bind< … >3.2.2 提取参数类型接下来,在3.2.2 提取参数类型如果我们想知道传递给的参数类型,我们有几个选择。最简单且最不容易出错的方法是将它们转发到一个函数模板,并让*它*进行类型推导
参数值的两倍?在这种情况下,我们可以使用之前介绍的forward元函数operator|:
BOOST_PARAMETER_NAME(s1) BOOST_PARAMETER_NAME(s2) BOOST_PARAMETER_NAME(s3) template <typename ArgumentPack> std::string f(ArgumentPack const& args) { std::string const& s1 = args[_s1]; std::string const& s2 = args[_s2]; typename parameter::binding< ArgumentPack,tag::s3,std::string >::type s3 = args[_s3 | (s1 + s2)]; // always constructs s1 + s2 return s3; } std::string x = f(( _s1="hello,", _s2=" world", _s3="hi world" ));
请注意,如果我们使用了,我们最终将返回对在2 * …表达式中创建的临时对象的引用。3.2.3 惰性默认计算当默认值的计算成本很高时,最好避免它,直到我们确定它绝对必要为止。为我们解决了这个问题,但是当显式使用 ArgumentPack 时,我们需要一个不同于
typename parameter::binding< ArgumentPack,tag::s3,std::string >::type s3 = args[ _s3 || boost::bind( std::plus<std::string>(), boost::ref(s1), boost::ref(s2) ) ];
表达式来*惰性*(即仅在需要时)计算默认值,以创建函数对象。要记住表达式中创建的临时对象的引用。之间的区别,请回想
通常使用短路评估:它的第二个参数只有在其第一个参数为
,元数范围从时才会被评估。同样,在
namespace people { namespace tag { struct name { typedef boost::parameter::forward_reference qualifier; }; struct age { typedef boost::parameter::forward_reference qualifier; }; } namespace // unnamed { boost::parameter::keyword<tag::name>& name = boost::parameter::keyword<tag::name>::instance; boost::parameter::keyword<tag::age>& age = boost::parameter::keyword<tag::age>::instance; } BOOST_PARAMETER_FUNCTION( (void), g, tag, (optional (name, *, "bob")(age, *, 42)) ) { std::cout << name << ":" << age; } void f(int age) { :vellipsis:` . . . ` g(age = 3); // whoops! } }
bind(std::plus<std::string>(), ref(s1), ref(s2))3AugmentingPathFinderage参数到使用标准和运行时支持库的调试版本。产生一个*函数对象*,当调用时,它将两个字符串相加。只有在调用者没有提供f的age参数时,才会调用该函数。使用标准和运行时支持库的调试版本。现在您应该对如何使用参数库有了相当好的理解。本节指出了一些更边缘的问题,这将帮助您更有效地使用该库。使用标准和运行时支持库的调试版本。会在我们所有关键字对象的名称前加上下划线前缀,以避免以下通常悄无声息的错误名称系统消息:警告/2(age/root/project/libs/parameter/doc/index.rst3:42,第 2471 行);*反向链接*
内联解释文本或短语引用起始字符串,但没有结束字符串。age尽管在上面的情况下,用户试图传递值const,但发生的情况是参数被重新赋值为 3,然后作为位置参数传递给。由于
的第一个位置参数是
namespace lib { BOOST_PARAMETER_NAME(name) BOOST_PARAMETER_NAME(index) BOOST_PARAMETER_FUNCTION( (int), f, tag, (optional (name,*,"bob")(index,(int),1)) ) { std::cout << name << ":" << index; std::cout << std::endl; return index; } }
,因此使用了
的默认值,并且 g 打印
int x = lib::f( lib::_name = "jill" , lib::_index = 1 );
。我们的下划线前缀命名约定使此问题发生的可能性降低。
在这种特殊情况下,如果 f 的
using lib::_name; using lib::_index; int x = lib::f(_name = "jill", _index = 1);
参数被设为
,则可以检测到问题,这在可能的情况下始终是一个好主意。最后,我们建议您为所有代码使用一个封闭命名空间,特别是对于带有下划线的名称。如果我们在上面省略了
using namespace lib; int x = f(_name = "jill", _index = 3);
people--stagedir=命名空间,那么全局命名空间中以带有下划线开头的名称(这些名称保留给您的 C++ 编译器)可能会与我们未命名命名空间中的名称变得无法挽回地模糊不清。
在我们的示例中,我们始终在(未命名命名空间中的)与使用这些关键字的 Boost.Parameter 启用函数相同的命名空间中声明关键字对象
namespace lib { namespace keywords { BOOST_PARAMETER_NAME(name) BOOST_PARAMETER_NAME(index) } BOOST_PARAMETER_FUNCTION( (int), f, keywords::tag, (optional (name,*,"bob")(index,(int),1)) ) { std::cout << name << ":" << index; std::cout << std::endl; return index; } }
这些函数的用户有几种选择--stagedir=:
using namespace lib::keywords; int y = lib::f(_name = "bob", _index = 2);
通过*using-declarations*使关键字对象可用
这个版本在实际调用站点上好得多,但*using-declarations*本身可能冗长且难以管理。#define使用*using-directive*引入整个命名空间BOOST_PARAMETER_HAS_PERFECT_FORWARDING此选项很方便,但它不加区别地使的*整个*内容无需限定即可使用。但是,如果我们在关键字声明周围添加一个额外的命名空间,我们可以给用户更多的控制const现在,用户只需一个*using-directive*即可引入与
如果您的编译器充分符合 C++11 标准,那么参数库将#define使用*using-directive*引入整个命名空间BOOST_PARAMETER_CAN_USE_MP11,除非您手动禁用它。singular.cpp、compose.cpp、optional_deduced_sfinae.cpp 和 deduced_dependent_predicate.cpp 测试程序演示了对 Boost.MP11 的支持。
一些旧编译器不支持 SFINAE。如果您的编译器符合该标准,则 Boost 头文件将#define预处理器符号BOOST_NO_SFINAE,并且启用参数的函数不会根据其签名从重载集中删除。sfinae.cpp 和 optional_deduced_sfinae.cpp 测试程序演示了 SFINAE 支持。
惰性默认计算依赖于result_of类模板,根据构造它们的函数对象的类型计算默认参数的类型。在不支持result_of, BOOST_NO_RESULT_OF将等于#define的编译器上,编译器将期望函数对象包含一个名为result_type的嵌套类型,该类型指示在不带参数调用时其返回类型。要在这些编译器上使用普通函数作为默认生成器,您需要将其包装在一个提供result_type作为typedef并通过其operator().
调用函数的类中。
namespace graphs { using graphs::graph; using graphs::visitor; using graphs::root_vertex; using graphs::index_map; using graphs::color_map; }
如果您使用 Microsoft Visual C++ 6.x,您可能会发现编译器在查找关键字对象时遇到问题。这个问题已经观察到,但仅在此一个编译器上,并且随着测试代码的演变而消失,因此我们建议您仅将其作为最后手段,而不是预防措施。解决方案是添加*using-declarations*以强制名称在封闭命名空间中无需限定即可用
请点击此链接,获取有关如何使用Boost.Python将 Boost.Parameter 启用函数暴露给 Python 的文档。
请点击此链接,查阅 Boost.Parameter 参考文档。
实际传递给函数或类模板的值。f在函数或类模板中引用参数的名称。例如,x的*参数*3:
int f(int x) { return x + 1; } int y = f(3);
的值由*参数*
[1] | 截至 Boost 1.33.0,图库仍在使用较旧的命名参数机制,但计划在即将发布的版本中将其更改为使用 Boost.Parameter(本库),同时保留旧接口以实现向后兼容性。 |
[2] | **一次定义规则**指出,C++ 程序中的任何给定实体在其构成程序的所有翻译单元(目标文件)中必须具有相同的定义。 |
[3] | 如果您不熟悉 Boost Graph Library,请不要担心您遇到的任何 Graph-library-specific 细节的含义。在这种情况下,您可以将文本中所有提及的顶点描述符类型替换为int,您对 Parameter 库的理解不会受到影响。 |
[4] | 这是 C++20 约束的主要动机。 |
[5] | (1, 2) 这里我们假设存在一个谓词元函数is_keyword_expression,可用于识别 Boost.Python 的 KeywordExpression 概念的模型。 |
[6] | 您总是可以通过应用*using-declaration*来制造函数存在于外部命名空间的假象 namespace foo_overloads { // foo declarations here void foo() { ... } ... } using foo_overloads::foo; 这种避免意外参数依赖查找的技术归功于 Herb Sutter。 |
[7] | 此功能取决于您的编译器对 SFINAE 的支持。**SFINAE**:**S**ubstitution **F**ailure **I**s **N**ot **A**n **E**rror(替换失败不是错误)。如果在函数模板实例化期间类型替换导致无效类型,则不会发出编译错误;相反,重载将从重载集中删除。通过根据某些条件的结果在函数签名中生成无效类型,我们可以决定在重载解析期间是否考虑重载。该技术在enable_if实用程序中形式化。大多数最新的编译器都支持 SFINAE;在不支持 SFINAE 的编译器上,Boost 配置库将#define符号BOOST_NO_SFINAE。有关 SFINAE 的更多信息,请参阅http://www.semantics.org/once_weakly/w02_SFINAE.pdf。 |