我最近遇到了很多情况,其中命名参数习语很有用,但我希望它在编译时得到保证。在链中返回引用的标准方法似乎总是调用运行时构造函数(使用 Clang 3.3 -O3 编译)。
我无法找到与此相关的任何内容,所以我试图让它与 constexpr 一起工作并获得一些功能:
class Foo
{
private:
int _a;
int _b;
public:
constexpr Foo()
: _a(0), _b(0)
{}
constexpr Foo(int a, int b)
: _a(a), _b(b)
{}
constexpr Foo(const Foo & other)
: _a(other._a), _b(other._b)
{}
constexpr Foo SetA(const int a) { return Foo(a, _b); }
constexpr Foo SetB(const int b) { return Foo(_a, b); }
};
...
Foo someInstance = Foo().SetB(5).SetA(2); //works
虽然这对于少量参数来说没问题,但对于较大的参数,它很快就会变得一团糟:
//Unlike Foo, Bar takes 4 parameters...
constexpr Bar SetA(const int a) { return Bar(a, _b, _c, _d); }
constexpr Bar SetB(const int b) { return Bar(_a, b, _c, _d); }
constexpr Bar SetC(const int c) { return Bar(_a, _b, c, _d); }
constexpr Bar SetD(const int d) { return Bar(_a, _b, _c, d); }
有没有更好的方法?我正在考虑使用具有许多(30 多个)参数的类来执行此操作,如果将来进行扩展,这似乎很容易出错。
编辑: 删除了 C++1y 标签——虽然 C++1y 似乎确实解决了问题(感谢 TemplateRex!)这是用于生产代码,我们坚持使用 C++ 11.如果这意味着它不可能,那么我想事情就是这样。
EDIT2:为了说明我为什么要找这个,这里有一个用例。目前在我们的平台上,开发人员需要为硬件配置显式设置位 vector ,虽然这没问题,但很容易出错。有些正在使用 C99 扩展中的指定初始化器,这没问题但不是标准的:
HardwareConfiguration hardwareConfig = {
.portA = HardwareConfiguration::Default,
.portB = 0x55,
...
};
然而,大多数人甚至没有使用它,而只是输入一团数字。因此,作为一项工作改进,我想朝着这样的方向发展(因为它也会强制使用更好的代码):
HardwareConfiguration hardwareConfig = HardwareConfiguration()
.SetPortA( Port().SetPolarity(Polarity::ActiveHigh) )
.SetPortB( Port().SetPolarity(Polarity::ActiveLow) );
这可能会更冗长,但在稍后阅读时会更清晰。
最佳答案
使用模板元编程
这是我为解决您的问题(至少部分解决)而想出的办法。通过使用模板元编程,您可以利用编译器为您完成大部分工作。对于那些以前从未见过此类代码的人来说,这些技术看起来很奇怪,但值得庆幸的是,大部分复杂性都可以隐藏在 header 中,用户只需以简洁明了的方式与库进行交互。
示例类定义及其使用
这里是一个例子,说明定义一个类需要你做什么:
template <
//Declare your fields here, with types and default values
typename PortNumber = field<int, 100>,
typename PortLetter = field<char, 'A'>
>
struct MyStruct : public const_obj<MyStruct, PortNumber, PortLetter> //Derive from const_obj like this, passing the name of your class + all field names as parameters
{
//Your setters have to be declared like this, by calling the Set<> template provided by the base class
//The compiler needs to be told that Set is part of MyStruct, probably because const_obj has not been instantiated at this point
//in the parsing so it doesn't know what members it has. The result is that you have to use the weird 'typename MyStruct::template Set<>' syntax
//You need to provide the 0-based index of the field that holds the corresponding value
template<int portNumber>
using SetPortNumber = typename MyStruct::template Set<0, portNumber>;
template<int portLetter>
using SetPortLetter = typename MyStruct::template Set<1, portLetter>;
template<int portNumber, char portLetter>
using SetPort = typename MyStruct::template Set<0, portNumber>
::MyStruct::template Set<1, portLetter>;
//You getters, if you want them, can be declared like this
constexpr int GetPortNumber() const
{
return MyStruct::template Get<0>();
}
constexpr char GetPortLetter() const
{
return MyStruct::template Get<1>();
}
};
使用类
int main()
{
//Compile-time generation of the type
constexpr auto myObject =
MyStruct<>
::SetPortNumber<150>
::SetPortLetter<'Z'>();
cout << myObject.GetPortNumber() << endl;
cout << myObject.GetPortLetter() << endl;
}
大部分工作由 const_obj 完成模板。它提供了一种在编译时修改对象的机制。很像 Tuple ,使用基于 0 的索引访问字段,但这不会阻止您使用友好名称包装 setter,就像上面的 SetPortNumber 和 SetPortLetter 所做的那样。 (他们只是转发到 Set<0> 和 Set<1>)
关于存储
在当前的实现中,在调用了所有的 setter 并声明了对象之后,字段最终存储在一个 const unsigned char 的紧凑数组中。名为 data在基类中。如果您使用的字段不是无符号字符(例如上面使用 PortNumber 所做的那样),则该字段将分为 big endien unsigned char的(可以根据需要更改为 little endien)。如果您不需要具有实际内存地址的实际存储,则可以通过修改 packed_storage 来完全省略它。 (请参阅下面的完整实现链接)并且这些值在编译时仍然可以访问。
限制
此实现仅允许整数类型用作字段(所有类型的短裤、整数、长整数、 bool 值、字符)。不过,您仍然可以提供作用于多个字段的 setter 。示例:
template<int portNumber, char portLetter>
using SetPort = typename MyStruct::template Set<0, portNumber>::
MyStruct::template Set<1, portLetter>;
完整代码
可以在此处找到实现这个小库的完整代码:
附加说明
此代码已经过测试,适用于 g++ 和 clang 的 C++11 实现。 它已经好几个小时都没有经过测试,所以当然可能存在错误,但它应该为您提供一个良好的基础。希望对您有所帮助!
1>0>关于c++ - 带有 constexpr 的编译时命名参数习语,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19018642/
exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby中使用两个参数异步运行exe吗?我已经尝试过ruby命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何rubygems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除
我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server
我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere
我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"
我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)
两者都可以defsetup(options={})options.reverse_merge:size=>25,:velocity=>10end和defsetup(options={}){:size=>25,:velocity=>10}.merge(options)end在方法的参数中分配默认值。问题是:哪个更好?您更愿意使用哪一个?在性能、代码可读性或其他方面有什么不同吗?编辑:我无意中添加了bang(!)...并不是要询问nobang方法与bang方法之间的区别 最佳答案 我倾向于使用reverse_merge方法:option
我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano
当我在我的Rails应用程序根目录中运行rakedoc:app时,API文档是使用/doc/README_FOR_APP作为主页生成的。我想向该文件添加.rdoc扩展名,以便它在GitHub上正确呈现。更好的是,我想将它移动到应用程序根目录(/README.rdoc)。有没有办法通过修改包含的rake/rdoctask任务在我的Rakefile中执行此操作?是否有某个地方可以查找可以修改的主页文件的名称?还是我必须编写一个新的Rake任务?额外的问题:Rails应用程序的两个单独文件/README和/doc/README_FOR_APP背后的逻辑是什么?为什么不只有一个?
我没有找到太多关于如何执行此操作的信息,尽管有很多关于如何使用像这样的redirect_to将参数传递给重定向的建议:action=>'something',:controller=>'something'在我的应用程序中,我在路由文件中有以下内容match'profile'=>'User#show'我的表演Action是这样的defshow@user=User.find(params[:user])@title=@user.first_nameend重定向发生在同一个用户Controller中,就像这样defregister@title="Registration"@user=Use
对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一