摘要 | 使用此库编写可以按名称接受参数的函数和类模板 |
---|
new_window( "alert" , _width=10 , _titlebar=false ); smart_ptr< Foo , deleter<Deallocate<Foo> > , copy_policy<DeepCopy> > p(new Foo);
由于命名参数可以按任何顺序传递,因此当函数或模板具有多个具有有用默认值的参数时,它们特别有用。该库还支持*推导*参数:也就是说,可以根据其类型推断其身份的参数。
作者 | David Abrahams, Daniel Wallin |
---|---|
联系方式 | [email protected], [email protected] |
组织 | 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!
**推导参数**可以在*不*提供显式参数名称的任何位置传递。函数具有可以根据传递的参数类型唯一标识的参数的情况并不少见。name参数到new_window就是一个这样的例子。其他参数(如果有效)都不能合理地转换为char const*。使用推导的参数接口,我们可以在*任何*参数位置传递窗口名称而不会引起歧义
window* w = new_window( movable_=false , "alert box" ); // OK! window* w = new_window( "alert box" , movable_=false ); // OK!
适当使用,推导的参数接口可以减轻用户甚至记住形式参数名称的负担。
我们为命名和推导的参数接口给出的理由同样适用于类模板和函数。使用参数库,我们可以创建允许显式命名模板参数(在本例中为shared和Client)的接口,如下所示
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 图库 的 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 | 输入 | 关联图 和 顶点列表图 的模型 | 无 - 此参数是必需的。 |
visitor | 输入 | DFS 访问器 的模型 | boost::dfs_visitor<>() |
root_vertex | 输入 | graph的顶点描述符类型。 | *vertices(graph).first |
index_map | 输入 | 具有键类型 := 的 可读属性映射 模型graph的顶点描述符和值类型为整数类型。 | get(boost::vertex_index,graph) |
color_map | 输入 / 输出 | 具有键类型 := 的 读/写属性映射 模型graph的顶点描述符类型。 | 一个boost::iterator_property_map创建自std::vector的default_color_type大小为num_vertices(graph)并使用index_map作为索引映射。 |
不要被上面第二列和第三列中的信息吓倒。就本练习而言,您无需详细了解它们。
此练习的重点是使调用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) }
您在此处看到的graphkeyword 的声明等效于
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的 *keyword 标记类型* 和一个名为_graph.
的 *keyword 对象* 引用。 当多个翻译单元中的函数模板使用命名参数接口时,这种涉及未命名命名空间和引用的“花哨操作”都是为了避免违反单一定义规则 (ODR)2(MSVC6.x 用户请参阅此注释)。
现在我们已经定义了关键字,函数模板定义遵循使用BOOST_PARAMETER_FUNCTION宏的简单模式
#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 } }
的参数是BOOST_PARAMETER_FUNCTIONBOOST_PARAMETER_FUNCTION
函数签名被描述为一个或两个相邻的带括号的术语(Boost.Preprocessor 序列),它们按位置传递时预期的顺序描述函数的参数。任何必需的参数必须放在首位,但(required … )子句可以在所有参数都是可选时省略。
必需参数首先给出——嵌套在(required … )(required … )
(required (graph, *) )
子句中——作为一系列描述每个参数名称和对参数类型的任何要求的双元素元组。在这种情况下,只有一个必需的参数,因此只有一个元组depth_first_search由于graphdepth_first_search(required … )不要求其
参数的任何特定类型,我们使用星号来指示允许任何类型。必需的参数必须始终位于签名中任何可选参数之前,但如果*没有*必需的参数,则可以完全省略(required … )子句。
(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) ) )
可选参数——嵌套在(optional … )子句中——作为一系列相邻的*三*元素元组给出,描述参数名称、对参数类型的任何要求,*以及*表示参数默认值的表达式2.1.5.3 处理“输入”、“输出”、“消耗/移出”和“转发”参数默认情况下,Boost.Parameter 将所有参数视为*转发*参数,函数将通过右值引用获取这些参数,并且仅常量左值、可变左值、常量右值或可变右值。因此,默认配置为用户代码提供了最大的灵活性。然而
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宏的简单模式#defined编译 compose.cpp 测试程序。您应该会遇到由特定约束违反引起的编译器错误。
当参数按位置传递(不使用关键字)时,它们将按照参数在签名中给出的顺序映射到参数,例如在此调用中
graphs::depth_first_search(x, y);
x将始终被解释为图,并且y将始终被解释为访问者。
请注意,在我们的示例中,图参数的值用于root_vertex, index_map和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).first参数来说,诸如graph之类的默认表达式是格式错误的,但这两个调用都将编译,并且每个调用都将打印完全相同的内容。
实际上,函数签名非常通用,以至于任何对depth_first_search的调用(参数少于五个)都将匹配我们的函数,前提是我们为所需的graph参数传递 _某些内容_。起初这似乎不是问题;毕竟,如果参数与depth_first_search实现施加的要求不匹配,则在实例化其主体时,稍后会出现编译错误。
非常通用的函数签名至少存在三个问题。
当传递的参数类型不合适时,阻止函数参与重载解析通常是一个好主意。当未提供所需的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");
我们真的不希望编译器考虑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_ > { }; };
此编码是一个 MPL 二元元函数类,一个具有嵌套apply元函数的类型,该元函数接收两个模板参数。对于第一个模板参数,Boost.Parameter 将传入我们将对其施加要求的类型。对于第二个模板参数,Boost.Parameter 将传入整个参数包,从而可以通过depth_first_searchvalue_type元函数和相应的关键字标记类型提取其他每个参数的类型。如果type元函数的结果apply等价于boost::mpl::true_,则T满足我们通过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) ) ) )
通常不需要如此完整地编码泛型函数参数的类型要求。但是,这样做是值得的:您的代码将更具自文档性,并且通常会提供更好的用户体验。您还可以更轻松地过渡到具有 对约束和概念的语言支持 的 C++20 标准。
将类型要求编码到函数的参数上对于使函数具有推导的参数接口至关重要。让我们回顾一下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);
为了进一步说明推导参数支持,请考虑 def 函数(来自 Boost.Python)的示例。其签名大致如下
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时,只需要两个参数。任何其他参数与其参数之间的关联可以通过实际传递的参数类型来确定,因此调用者既不需要记住参数位置,也不需要为这些参数显式指定参数名称。要使用BOOST_PARAMETER_FUNCTION生成此接口,我们只需将推导的参数括在(deduced …)子句中,如下所示
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() ) ) ) ) { … }
语法注释
一个(deduced …)子句始终包含一个(required …)和/或一个(optional …)子子句,并且必须位于指示外层非推导参数的任何(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" );
deduced.cpp 和 deduced_dependent_predicate.cpp 测试程序演示了推导参数支持的其他用法。
对于某些算法,返回类型取决于至少一种参数类型。但是,在使用BOOST_PARAMETER_FUNCTION或其他代码生成宏对这种返回类型进行编码时,需要注意一个陷阱。例如,给定以下定义
BOOST_PARAMETER_NAME(x) BOOST_PARAMETER_NAME(y) BOOST_PARAMETER_NAME(z)
让我们的算法简单地返回它的一个参数。如果我们天真地根据parameter::value_type:
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!
问题在于,即使y是一个必需的参数,BOOST_PARAMETER_FUNCTION也会生成某些重载,对于这些重载,参数包类型Args实际上并不包含关键字标记类型tag::y。解决方案是使用 SFINAE 来排除这些重载的生成。由于parameter::value_type是一个元函数,我们的工具是lazy_enable_if:
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; }
有关工作演示,请参见 preprocessor_deduced.cpp。
BOOST_PARAMETER_MEMBER_FUNCTION和BOOST_PARAMETER_CONST_MEMBER_FUNCTION宏接受与BOOST_PARAMETER_FUNCTION完全相同的参数,但设计用于在类的正文中使用
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(); }
这些宏并不能直接允许将函数的接口与其实现分离,但您可以始终将参数转发到单独的实现函数。
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. };
要公开静态成员函数,只需在函数名称前插入关键字“static”。
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(); }
BOOST_PARAMETER_FUNCTION_CALL_OPERATOR和BOOST_PARAMETER_CONST_FUNCTION_CALL_OPERATOR宏接受与BOOST_PARAMETER_MEMBER_FUNCTION和BOOST_PARAMETER_CONST_MEMBER_FUNCTION宏相同的参数(函数名称除外),因为这些宏允许将封闭类的实例视为函数对象。
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(); }
C++ 中缺少“委托构造函数”功能(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1986.pdf)在某种程度上限制了该库为定义参数化构造函数所能提供的接口质量。 通常的构造函数委托缺失的解决方法适用:必须将公共逻辑分解到一个或多个基类中。
让我们构建一个参数化构造函数,它只是打印其参数。 第一步是编写一个基类,其构造函数接受一个称为 参数包 的参数:一组对实际参数的引用,并用其关键字标记。 实际参数的值是通过使用关键字对象对 参数包 进行*索引*来提取的。
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; } };
请注意,当应用于传递给 参数包 的索引运算符的关键字对象时,按位或(“||”)运算符具有特殊含义:它用于指示默认值。 在这种情况下,如果 参数包 中没有index42参数,则将使用
0
struct myclass : myclass_impl { BOOST_PARAMETER_CONSTRUCTOR( myclass, (myclass_impl), tag , (required (name,*)) (optional (index,*)) ) // no semicolon };
代替。”)运算符具有特殊含义:它用于指示默认值。 在这种情况下,如果 参数包 中没有现在我们准备编写参数化构造函数接口。name由于我们为nameindex
myclass x("bob", 3); // positional myclass y(_index = 12, _name = "sally"); // named myclass z("june"); // positional/defaulted
提供了默认值,但没有为
name
template < ValueType, BaseList = bases<> , HeldType = ValueType, Copyable = void > class class_;
提供默认值,因此只有name是必需的。 我们可以如下练习新的接口:
在本节中,我们将使用 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> > > …;
ValueType
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) }}
您在此处看到的是必需的。keyword 的声明等效于
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 命名模板参数首先,我们将构建一个允许用户按位置或按名称传递参数的接口。2.5.1.1 模板关键字是必需的。.
class_type它定义了一个名为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_ { … }; }}
class_type_的*参数传递模板*。2.5.1.2 类模板框架
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; }}
boost::parameter::void_作为默认值。2.5.1.3 类模板签名接下来,我们需要构建一个类型,称为 参数规范,用于描述boost::python::class_的“签名”。 参数规范 枚举了按位置顺序排列的必需和可选参数,以及任何类型要求(请注意,它*不*指定默认值——这些将在单独处理)。2.5.1.4 参数包和参数提取的“签名”。 参数规范 枚举了按位置顺序排列的必需和可选参数,以及任何类型要求(请注意,它*不*指定默认值——这些将在单独处理)。接下来,在class_的正文中,我们使用 参数规范 的嵌套
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;
value_type< … >
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>));
keyword< … >::get()很像和binding< … >,但不会向实际参数类型添加引用。 请注意,默认值是通过向其传递可选的第三个参数来指定的。2.5.2 练习到目前为止的代码回顾我们最初的例子,我们现在可以检查预期的参数。2.5.3 推导模板参数binding< … >要在此处应用推导的参数接口,我们只需要使类型要求更严格一些,以便held_type和我们现在可以检查预期的参数。copyable
namespace boost { namespace python { namespace detail { struct bases_base { }; } template < typename A0 = void, typename A1 = void, typename A2 = void … > struct bases : detail::bases_base { }; }}
参数可以与其他参数清晰地区分开。 Boost.Python 通过要求
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;
base_list
typedef boost::python::class_<B, boost::noncopyable> c1; typedef boost::python::class_< D, std::auto_ptr<D>, bases<B> > c2;
是其
bases< … >模板的特化(而不是任何旧的 MPL 序列),并通过要求copyable(如果显式提供)为:
BOOST_PARAMETER_NAME( ( object-name , tag-namespace ) parameter-name )
boost::noncopyable
BOOST_PARAMETER_NAME( ( pass_foo, keywords ) foo ) BOOST_PARAMETER_FUNCTION( (int), f, keywords, (required (foo, *)) ) { return foo + 1; } int x = f(pass_foo = 41);
来做到这一点。 识别
bases< … >
现在我们可以重写我们的签名,使所有三个可选参数都可推导。
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"));
如果您不喜欢用于引用关键字对象的前导下划线命名约定,或者您需要将名称
tag接下来,我们需要构建一个类型,称为 参数规范,用于描述用于关键字类型命名空间以外的其他用途,则还有另一种使用
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" ) );
的方法。至此,您应该对基础知识有了很好的了解。 在本节中,我们将介绍该库的一些更深奥的用法。以下是一个用法示例。
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]); }
但是,在使用这种更详细的形式之前,请阅读有关 关键字对象命名最佳实践 的部分。”)运算符具有特殊含义:它用于指示默认值。 在这种情况下,如果 参数包 中没有当我们查看 参数化构造函数 和 类模板 时,我们已经见过 参数包。 正如您可能已经猜到的那样,参数包 实际上是这个库所做一切的核心;在本节中,我们将研究更有效地构建和操作它们的方法。的“签名”。 参数规范 枚举了按位置顺序排列的必需和可选参数,以及任何类型要求(请注意,它*不*指定默认值——这些将在单独处理)。3.2.1 构建 参数包
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]; }
最简单的 参数包 是赋值给关键字对象的结果。class_此外,可以使用逗号运算符组合 参数包。 下面的额外括号用于防止编译器将两个单独的参数传递给的“签名”。 参数规范 枚举了按位置顺序排列的必需和可选参数,以及任何类型要求(请注意,它*不*指定默认值——这些将在单独处理)。print_name_and_indexcompose.cpp 测试程序展示了此功能的更多示例。要使用位置参数构建 参数包,我们可以使用 参数规范。 如 类模板签名 一节中所述,参数规范 描述了参数的位置顺序和任何相关的类型要求。 正如我们可以使用其嵌套的
模板构建 参数包 *类型*一样,我们也可以通过调用其函数调用运算符来构建 参数包 *对象*。BOOST_PARAMETER_FUNCTION3.2.2 提取参数类型如果我们想知道传递给:
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" ));
print_name_and_index的参数类型,我们有几个选择。 最简单且最不容易出错的方法是将它们转发给函数模板,并让*它*进行类型推导。有时需要在没有额外函数调用层的情况下推导参数类型。 例如,假设我们想返回index参数值的两倍? 在这种情况下,我们可以使用 前面 介绍的value_type< … >元函数。
typename parameter::binding< ArgumentPack,tag::s3,std::string >::type s3 = args[ _s3 || boost::bind( std::plus<std::string>(), boost::ref(s1), boost::ref(s2) ) ];
为我们解决了这个问题,但是当显式使用 参数包 时,我们需要一个除operator|之外的工具。index在上面的例子中,字符串
"hello, world"
(如果显式提供)为被构造了,尽管用户向我们传递了
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! } }
助记符3要记住operator|参数到和operator||3.2.3 延迟默认值计算之间的区别,请记住operator|operator||和通常使用短路求值:仅当其第一个参数为和falsename时才对其第二个参数进行求值。 同样,在operator|color_map[param || f]3:42中,仅当未提供
paramoperator|参数时才调用常量f。表达式
bind(std::plus<std::string>(), ref(s1), ref(s2))
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; } }
产生一个*函数对象*,当调用它时,它将两个字符串加在一起。 仅当调用者未提供
s3
int x = lib::f( lib::_name = "jill" , lib::_index = 1 );
参数时才会调用该函数。
通过 *using 声明* 使关键字对象可用
using lib::_name; using lib::_index; int x = lib::f(_name = "jill", _index = 1);
这个版本在实际调用位置好得多,但 *using 声明* 本身可能冗长且难以管理。
使用 *using 指令* 引入整个命名空间
using namespace lib; int x = f(_name = "jill", _index = 3);
此选项很方便,但它会不加选择地使lib的 *全部* 内容无需限定即可使用。
但是,如果我们在关键字声明周围添加一个额外的命名空间,我们可以为用户提供更多控制
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; } }
现在,用户只需要一个 *using 指令* 即可引入与之关联的所有关键字的名称lib:
using namespace lib::keywords; int y = lib::f(_name = "bob", _index = 2);
Boost.Parameter 支持的接口习惯用法对 C++ 来说是全新的,因此现有的文档约定无法满足其需求。
注意
此空间为空,因为我们尚未确定任何最佳实践。如果您有认为值得分享的风格,我们很乐意链接到您的文档。
使用 Parameter 库最新 Boost 版本的回归测试结果,查看其在您常用编译器上的运行情况。此外,您可能需要注意以下问题以及针对特定编译器的解决方法。
如果您的编译器支持完美转发,则 Parameter 库将#define宏BOOST_PARAMETER_HAS_PERFECT_FORWARDING除非您手动禁用它。如果您的编译器不提供此支持,则parameter::parameters::operator()会将右值引用视为左值常量引用以解决转发问题,因此在某些情况下,您必须在将绑定到输出参数的任何参数周围使用 boost::ref 或 std::ref 进行包装。evaluate_category.cpp 和 preprocessor_eval_category.cpp 测试程序演示了此支持。
如果您的编译器完全符合 C++11 标准,则 Parameter 库将#define宏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().
调用该函数。如果您使用 Microsoft Visual C++ 6.x,您可能会发现编译器难以找到您的关键字对象。仅在此编译器上观察到此问题,并且随着测试代码的演变而消失,因此我们建议您仅将其用作最后手段,而不是作为预防措施。解决方案是添加*using 声明*以强制名称在封闭命名空间中可用,而无需限定
namespace graphs { using graphs::graph; using graphs::visitor; using graphs::root_vertex; using graphs::index_map; using graphs::color_map; }
请访问此链接,了解如何使用Boost.Python 将启用 Boost.Parameter 的函数公开给 Python 的文档。
实际传递给函数或类模板的值。
在函数或类模板中用于引用参数的名称。例如, 的值3.2.3 延迟默认值计算的*形参*x由*实参*3:
int f(int x) { return x + 1; } int y = f(3);
给出。作者要感谢所有参与审查此库及其文档的 Boosters,尤其是我们的审查经理 Doug Gregor。
[1] | 截至 Boost 1.33.0,Graph 库仍在使用较旧的命名参数机制,但计划在即将发布的版本中将其更改为使用 Boost.Parameter(此库),同时保持旧接口向后兼容。 |
[2] | **单一定义规则** 指的是 C++ 程序中任何给定实体在构成程序的所有翻译单元(目标文件)中必须具有相同的定义。 |
[3] | 如果您不熟悉 Boost 图形库,请不要担心您遇到的任何特定于图形库的细节的含义。在这种情况下,您可以将文本中所有提到的顶点描述符类型替换为int,并且您对 Parameter 库的理解不会受到影响。 |
[4] | 这是C++20 约束背后的主要动机。 |
[5] | (1, 2)这里我们假设存在一个谓词元函数is_keyword_expression可以用来识别 Boost.Python 的 KeywordExpression 概念的模型。 |
[6] | 您始终可以通过应用*using 声明*来造成该函数位于外部命名空间的错觉 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;在不支持它的编译器上,Boost 配置库将#define符号BOOST_NO_SFINAE。有关 SFINAE 的更多信息,请参阅http://www.semantics.org/once_weakly/w02_SFINAE.pdf。 |