摘要 | 使用此库编写可按名称接受参数的函数和类模板 |
---|
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!
推导参数可以以任何位置传递,*无需*提供显式的参数名称。函数中存在可以根据传递的参数类型唯一识别的参数并不少见。parameter名称传递给new_window就是一个这样的例子。如果没有其他有效参数,它们都不能合理地转换为char const*。使用推导参数接口,我们可以在*任何*参数位置传递窗口名称,而不会引起歧义
window* w = new_window( movable_=false , "alert box" ); // OK! window* w = new_window( "alert box" , movable_=false ); // OK!
如果使用得当,推导参数接口可以免除用户记住形式参数名称的负担。
我们对命名参数和推导参数接口的推理同样适用于类模板和函数。使用 Parameter 库,我们可以创建允许模板参数(在这种情况下是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;
本教程将介绍所有基础知识——如何为函数模板和类模板构建命名参数和推导参数接口——以及一些更高级的用法。
在本节中,我们将展示如何使用 Parameter 库为 Boost Graph 库 的 depth_first_search 算法构建一个富有表现力的接口。1
Parameter 库的大多数组件都在以组件命名的头文件中声明。例如,
#include <boost/parameter/keyword.hpp>
将确保boost::parameter::keyword对编译器是已知的。还有一个组合头文件,boost/parameter.hpp,其中包含库的大部分组件。在本教程的其余部分,除非另有说明,您可以使用上述规则来确定要#include来访问库的任何给定组件的头文件。
此外,以下示例也将按照命名空间别名
namespace parameter = boost::parameter;
已声明的方式编写:我们将编写parameter::xxx而不是boost::parameter::xxx.
Graph 库的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 | 关联图 和 顶点列表图 的模型 | 无 - 此参数为必填。 |
visitor | in | DFS 访问器 的模型 | boost::dfs_visitor<>() |
root_vertex | in | graph的顶点描述符类型。 | *vertices(graph).first |
distance_compare(CompareFunction distance_compare) | in | 可读属性图 的模型,键类型 :=graph的顶点描述符和值类型为整数类型。 | get(boost::vertex_index,graph) |
color_map | in / out | 读/写属性图 的模型,键类型 :=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))
或move_from(…)中。为了查看当参数绑定到违反其类别约束的实参时会发生什么,尝试使用宏如果 BOOST_PARAMETER_HAS_PERFECT_FORWARDING 已LIBS_PARAMETER_TEST_COMPILE_FAILURE_0
LIBS_PARAMETER_TEST_COMPILE_FAILURE_1
graphs::depth_first_search(x, y);
x宏编译 compose.cpp 测试程序。您应该会遇到由特定约束违反引起的编译器错误。y2.1.5.4 位置参数
将始终被解释为图,并且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_search2.1.5.5 默认表达式求值
#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(); }
请注意,在我们的示例中,graph 参数的值用于的默认表达式中。如果为该参数传递了实际参数,则默认表达式永远不会被评估(甚至不会实例化)。我们可以通过将graph的主体替换为打印参数的内容来演示我们目前的代码
vertices(graph).firstdepth_first_search这样的默认表达式对于给定的graph参数来说是格式错误的,但两次调用都会编译,并且每次都将打印完全相同的内容。depth_first_search2.1.5.6 签名匹配和重载
事实上,函数签名是如此通用,以至于任何对
graphs::detail::depth_first_search_with_named_paramsgraph(或者更糟——想想您的 STL 实现中出现错误时会得到什么样的错误)。4
// new overload BOOST_PARAMETER_FUNCTION((void), depth_first_search, (tag), (required (graph,*))( … ) ) { // new algorithm implementation } … // ambiguous! depth_first_search(boost::adjacency_list<>(), 2, "hello");
隔离在一个不包含类型的命名空间中6,但是假设我们*希望*它通过 ADL 找到呢?depth_first_search当传入的实参类型不合适时,阻止函数被考虑进行重载解析通常是一个好主意。当未提供必需的root_vertex参数时,库已经这样做了,但我们不太可能看到一个不带图操作的深度优先搜索。相反,假设我们发现了一种可以在不模拟 Incidence Graph 的图上工作的不同深度优先搜索算法?如果我们只是添加一个简单的重载,它就会有歧义2.1.5.6.1 谓词要求我们确实不希望编译器考虑原始版本的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_ > { }; };
"hello"apply,不满足它必须匹配depth_first_search参数的顶点描述符类型的要求。相反,此调用应该只调用我们的新重载。为了使原始的value_type重载不再参与竞争,我们首先将此要求编码如下type创建,其大小为apply此编码是 MPL 二进制元函数类,一种具有嵌套元函数的类型,它接受两个模板参数。对于第一个模板参数,Boost.Parameter 将传入我们将施加要求的类型。对于第二个模板参数,Boost.Parameter 将传入整个参数包,从而可以通过ifT元函数和相应的关键字标签类型提取其他每个参数的类型。结果元函数将等同于boost::mpl::true_.
满足我们由*boost::is_convertible
(root_vertex , *(vertex_descriptor_predicate) , *vertices(graph).first )
语句施加的要求;否则,结果将等同于depth_first_searchboost::mpl::false_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) ) ) )
元函数的实现。这里没有空间详细描述图库的细节,但足以说明接下来的几个元函数类提供了必要的检查。
参数的默认值并非易事,因此最好将计算分解为单独的函数模板。new_window签名将每个谓词元函数括在括号中,*前面带星号*,如下所示
window* w = new_window( movable_=false , "alert box" ); window* w = new_window( "alert box" , movable_=false );
通常不需要如此完整地编码泛型函数参数的类型要求。但是,这样做是值得的:您的代码将更具自文档性,并且通常会提供更好的用户体验。您还将更容易过渡到 C++20 标准,其中包含 对约束和概念的语言支持。new_window2.1.5.6.2 更多关于类型要求*将类型要求编码到函数的参数中对于启用函数的推导参数接口至关重要。让我们回顾一下
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);
函数而不指定关键字。对于每个具有所需类型的参数,我们可以将该类型括在括号中,然后*替换*参数签名中的
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() );
元素
以下语句现在可以工作,并且彼此等效,也等效于之前的语句def2.1.5.7 推导参数forward为了进一步说明推导参数支持,请考虑 Boost.Python 中的 def 函数的示例。其签名大致如下的方括号运算符,以提取与相应命名参数(无下划线)绑定的相同参数,如下文所示。尽量不要被这个例子中“keywords”一词的使用分散注意力:尽管它在 Boost.Python 中与 Parameter 库中的含义相似,但为了本练习的目的,您可以认为它完全不同。
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 …)
和/或一个
char const* f_name = "f"; def( f_name , &f , some_policies , "Documentation for f" ); def( f_name , &f , "Documentation for f" , some_policies );
(optional …)policies子句,并且必须位于指示外部级别非推导参数的任何char const*子句之后。
def( f_name , &f , _policies = some_policies , "Documentation for f" );
有了上述声明,以下两个调用是等效的
参数,出于某种原因,它也可以转换为forward,她总是可以像这样明确指定参数名称
BOOST_PARAMETER_NAME(x) BOOST_PARAMETER_NAME(y) BOOST_PARAMETER_NAME(z)
deduced.cpp 和 deduced_dependent_predicate.cpp 测试程序演示了推导参数支持的其他用法。2.1.5.8 参数依赖的返回类型:
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; }
对于某些算法,返回类型取决于至少一个参数类型。但是,在使用return_y或其他代码生成宏时,在编码此返回类型时需要避免一个陷阱。例如,给定以下定义
std::map<char const*,std::string> k2s; assert("foo" == return_y(2, k2s, "foo")); // error!
让我们的算法简单地返回它的一个参数。如果我们天真地将其返回类型实现为yparameter::value_typeforward那么以任何方式使用Args而不是位置参数都将导致编译错误问题在于,即使是必需参数,2.1.5.8 参数依赖的返回类型也会生成某些重载,这些重载的参数包类型实际上不包含关键字标签类型:
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; }
tag::y
该。解决方案是使用 SFINAE 排除这些重载的生成。由于and是元函数,我们的工具是lazy_enable_ifforward有关工作演示,请参阅 preprocessor_deduced.cpp。
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_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(); }
该这些宏不能直接将函数接口与其实现分离,但您始终可以将参数转发到单独的实现函数and2.2.1 静态成员函数要公开静态成员函数,只需在函数名称前插入关键字“。解决方案是使用 SFINAE 排除这些重载的生成。由于and是元函数,我们的工具是”
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_FUNCTION_CALL_OPERATOR
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 是赋值给关键字对象的结果的方法。此外,ArgumentPack 可以使用逗号运算符组合。下面额外的括号用于防止编译器将两个单独的参数视为
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 提取参数类型接下来,在如果我们想知道传递给的参数类型,我们有几种选择。最简单且最不易出错的方法是将它们转发到函数模板并允许*它*进行类型推导偶尔需要在不增加函数调用层的情况下推导参数类型。例如,假设我们想返回
元函数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! } }
产生一个*函数对象*,当被调用时,它将两个字符串相加。该函数只有在调用者未提供3AugmentingPathFinderage传递给使用标准和运行时支持库的调试版本。参数时才会被调用。f的age现在您应该对如何使用 Parameter 库有了一个相当好的了解。本节指出了一些有助于您更有效地使用该库的更多边缘问题。使用标准和运行时支持库的调试版本。在所有关键字对象的名称前加上下划线,以避免以下通常是静默的错误使用标准和运行时支持库的调试版本。系统消息:警告/2 (名称/root/project/libs/parameter/doc/index.rstage,第 2471 行);反向链接3:42内联解释文本或短语引用起始字符串,但没有结束字符串。
尽管在上面的情况下,用户试图传递值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);
,则可以检测到问题,这在可能的情况下始终是一个好主意。最后,我们建议您为所有代码使用一个封闭命名空间,特别是对于带有前导下划线的名称。如果我们在上面省略了
people
using namespace lib; int x = f(_name = "jill", _index = 3);
命名空间,那么全局命名空间中以带有前导下划线的名称(这些名称保留给您的 C++ 编译器)可能会与我们匿名命名空间中的名称产生不可挽回的歧义。--stagedir=在我们的示例中,我们始终在(匿名命名空间内的)与使用这些关键字的 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-directive 引入整个命名空间#define此选项很方便,但它不加区别地使BOOST_PARAMETER_HAS_PERFECT_FORWARDING的*全部*内容在未限定的情况下可用。但是,如果我们在关键字声明周围添加一个额外的命名空间,我们可以给用户更多的控制现在用户只需要一个 using-directive 即可引入与const关联的所有关键字的名称。
Boost.Parameter 启用的接口惯用语对 C++ 来说是全新的,因此现有文档约定无法满足其需求。#define此选项很方便,但它不加区别地使BOOST_PARAMETER_CAN_USE_MP11这个空间是空的,因为我们还没有确定任何最佳实践。如果您有您认为值得分享的文档风格,我们将非常乐意链接到您的文档。
使用 Parameter 库最新 Boost 版本的回归测试结果 来查看它在您喜欢的编译器上的表现。此外,您可能需要注意以下特定编译器的问题和解决方法。#define如果您的编译器支持 完美转发,那么 Parameter 库将BOOST_NO_SFINAE宏
parameter::parameters::operator()result_of会将右值引用视为左值result_of, 引用以解决 转发问题,因此在某些情况下,您必须在任何将绑定到 out 参数的参数周围包裹 boost::ref 或 std::ref。evaluate_category.cpp 和 preprocessor_eval_category.cpp 测试程序演示了此支持。将等于#define如果您的编译器足够符合 C++11 标准,那么 Parameter 库将result_type除非您手动禁用它。singular.cpp、compose.cpp、optional_deduced_sfinae.cpp 和 deduced_dependent_predicate.cpp 测试程序演示了对 Boost.MP11 的支持。result_type一些较旧的编译器不支持 SFINAE。如果您的编译器符合该标准,那么 Boost 头文件将typedef预处理器符号operator().
,并且启用参数的函数不会根据其签名从重载集中删除。sfinae.cpp 和 optional_deduced_sfinae.cpp 测试程序演示了 SFINAE 支持。
namespace graphs { using graphs::graph; using graphs::visitor; using graphs::root_vertex; using graphs::index_map; using graphs::color_map; }
5.4 不支持 result_of
类模板来计算给定构造它们的函数对象类型的默认参数类型。在不支持
BOOST_NO_RESULT_OFfd 的编译器上,编译器会期望函数对象包含一个嵌套类型名,x,它指示在不带参数调用时其返回类型。要在这些编译器上将普通函数用作默认生成器,您需要将其封装在一个类中,该类提供3:
int f(int x) { return x + 1; } int y = f(3);
作为
[1] | ,并通过其 |
[2] | 一次定义规则规定,C++ 程序中的任何给定实体在构成该程序的所有翻译单元(目标文件)中必须具有相同的定义。 |
[3] | 如果您不熟悉 Boost Graph 库,请不要担心您遇到的任何 Graph 库特定细节的含义。在这种情况下,您可以将文本中所有提及顶点描述符类型的地方替换为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:Substitution Failure Is Not An Error(替换失败不是错误)。如果在函数模板实例化期间类型替换导致无效类型,则不会发出编译错误;相反,重载将从重载集中移除。通过根据某些条件的结果在函数签名中生成无效类型,我们可以决定在重载解析期间是否考虑重载。该技术在 enable_if 工具中进行了形式化。大多数最新的编译器都支持 SFINAE;在不支持它的编译器上,Boost 配置库将#define符号BOOST_NO_SFINAE。有关 SFINAE 的更多信息,请参阅 http://www.semantics.org/once_weakly/w02_SFINAE.pdf。 |