简体   繁体   English

使用constexpr编译时命名参数成语

[英]Compile-Time Named Parameter Idiom with constexpr

I've recently run into a quite a few situations where the Named Parameter Idiom would be useful, but I'd like it to be guaranteed in compile-time. 我最近遇到过很多情况,命名参数成语会很有用,但我希望它能在编译时得到保证。 The standard method of returning references in a chain almost always appears to invoke a run-time constructor (compiling with Clang 3.3 -O3). 返回链中引用的标准方法几乎总是会调用运行时构造函数(使用Clang 3.3 -O3进行编译)。

I haven't been able to find anything with reference to this so I tried to get this to work with constexpr and got something functional: 我没有找到任何参考这个的东西所以我试图constexprconstexpr一起工作并得到一些功能:

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

While this is okay for a small number of parameters, for larger numbers it quickly blows up into a mess: 虽然这对于少量参数是可以接受的,但对于较大的数字,它很快就会变得混乱:

    //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); }

Is there a better way? 有没有更好的办法? I'm looking at doing this with classes that have many (30+) parameters and this seems like it would be prone to error if extended in the future. 我正在考虑使用具有许多(30+)参数的类来执行此操作,如果将来扩展,这似乎很容易出错。

EDIT: Removed C++1y tag -- while C++1y does appear to fix the problem (thanks TemplateRex!) this is for production code, and we are stuck with C++11. 编辑:删除了C ++ 1y标签 - 虽然C ++ 1y似乎确实解决了问题(感谢TemplateRex!)这是生产代码,我们仍然坚持使用C ++ 11。 If that means its impossible, then I guess that's just the way it is. 如果这意味着它是不可能的,那么我想这就是它的方式。

EDIT2: To show why I'm looking for this, here's a use case. 编辑2:为了说明我为什么要这样做,这是一个用例。 Currently with our platform, developers need to explicitly set bit vectors for hardware configurations, and while this is okay it's very error prone. 目前使用我们的平台,开发人员需要明确设置硬件配置的位向量,虽然这是可以的,但它非常容易出错。 Some are using designated initializers from the C99 extension, which is okay but non-standard: 有些人正在使用C99扩展中的指定初始值设定项,这是可以的但非标准的:

HardwareConfiguration hardwareConfig = {
    .portA = HardwareConfiguration::Default,
    .portB = 0x55,
    ...
};

Most, however, aren't even using this, and are just inputting a blob of numbers. 然而,大多数人甚至没有使用它,只是输入了一大堆数字。 So as a working improvement, I'd like to move towards something like this (since it also forces better code): 因此,作为一项工作改进,我想转向这样的事情(因为它也会强制更好的代码):

HardwareConfiguration hardwareConfig = HardwareConfiguration()
    .SetPortA( Port().SetPolarity(Polarity::ActiveHigh) )
    .SetPortB( Port().SetPolarity(Polarity::ActiveLow) );

Which might be far more verbose, but much clearer when reading later. 这可能更加冗长,但在稍后阅读时会更加清晰。

Using Template Metaprogramming 使用模板元编程

Here is something I came up with to solve your problem (at least partially). 这是我想出来解决你的问题(至少部分)。 By using template metaprogramming, you can leverage the compiler to do most of the job for you. 通过使用模板元编程,您可以利用编译器为您完成大部分工作。 These techniques look weird for those who have never seen such code before, but thankfully most of the complexity can be hidden away in a header and the users only interact with the library in a neat and terse manner. 对于那些以前从未见过这样的代码的人来说,这些技术看起来很奇怪,但幸运的是,大多数复杂性都可以隐藏在标题中,用户只能以简洁的方式与库进行交互。

A Sample Class Definition and its Use 样本类定义及其使用

Here is an example of what defining a class would entail on your part: 下面是一个定义类会对您造成什么影响的示例:

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>();
    }
};

Using the Class 使用班级

int main()
{
    //Compile-time generation of the type
    constexpr auto myObject = 
        MyStruct<>
        ::SetPortNumber<150>
        ::SetPortLetter<'Z'>();

    cout << myObject.GetPortNumber() << endl;
    cout << myObject.GetPortLetter() << endl;
}

Most of the job is done by the const_obj template. 大多数工作都是由const_obj模板完成的。 It provides a mechanism to modify your object at compile time. 它提供了一种在编译时修改对象的机制。 Much like a Tuple , the fields are accessed with 0-based indices but this does not stop you from wrapping the setters with friendly names, as is done with SetPortNumber and SetPortLetter above. 就像一个Tuple ,字段可以通过基于0的索引访问,但这并不能阻止你用友好的名字包装setter,就像上面的SetPortNumber和SetPortLetter一样。 (They just forward to Set<0> and Set<1>) (他们只是转发到Set <0>和Set <1>)

About Storage 关于存储

In the current implementation, after all the setters have been called and the object declared, the fields end up being stored in a compact array of const unsigned char 's named data in the base class. 在当前实现中,在调用所有setter并声明对象之后,这些字段最终存储在基类中const unsigned char的命名data的紧凑数组中。 If you use fields that are not unsigned chars (as in done above with PortNumber for example) the field is divided in big endien unsigned char 's (could be changed to little endien as needed). 如果你使用的字段不是无符号字符(例如上面用PortNumber完成的那样),则字段被分成大的endien unsigned char (可以根据需要更改为little endien)。 If you don't need an actual storage that has an actual memory address, you could omit it altogether by modifying the packed_storage (see full implementation link below) and the values would still be accessible at compile time. 如果您不需要具有实际内存地址的实际存储,则可以通过修改packed_storage (请参阅下面的完整实现链接)完全省略它,并且仍然可以在编译时访问这些值。

Limitations 限制

This implementation only allows integral types to be used as fields (all flavors of shorts, ints, longs, bool, char). 此实现仅允许将整数类型用作字段(所有类型的short,int,long,bool,char)。 You can still provide setters that act on more than one field though. 您仍然可以提供作用于多个字段的setter。 Example: 例:

template<int portNumber, char portLetter>
using SetPort = typename MyStruct::template Set<0, portNumber>::
                         MyStruct::template Set<1, portLetter>;

Full Code 完整代码

The full code for the implementation of this little library can be found here: 可以在此处找到实现此小库的完整代码:

Full Implementation 全面实施

Additional Notes 补充说明

This code has been tested and works with the C++11 implementation of both g++ and clang. 此代码已经过测试,可以与g ++和clang的C ++ 11实现一起使用。 It has not been tested for hours and hours so of course there may be bugs but it should provide you with a good base to start with. 它没有经过几小时和几小时的测试,因此当然可能存在错误,但它应该为您提供良好的基础。 I hope this helps! 我希望这有帮助!

In C++14, constraints on constexpr function will be relaxed, and the usual chaining of reference-returning setters will work at compile-time: 在C ++ 14中,对constexpr函数的约束将被放宽,并且引用返回setter的常规链接将在编译时工作:

#include <iostream>
#include <iterator>
#include <array>
#include <utility>

class Foo
{
private:
    int a_ = 0;
    int b_ = 0;
    int c_ = 0;
    int d_ = 0;

public:
    constexpr Foo() = default;

    constexpr Foo(int a, int b, int c, int d)
    : 
        a_{a}, b_{b}, c_{c}, d_{d}
    {}

    constexpr Foo& SetA(int i) { a_ = i; return *this; }
    constexpr Foo& SetB(int i) { b_ = i; return *this; }
    constexpr Foo& SetC(int i) { c_ = i; return *this; }
    constexpr Foo& SetD(int i) { d_ = i; return *this; }

    friend std::ostream& operator<<(std::ostream& os, const Foo& f)
    {
        return os << f.a_ << " " << f.b_ << " " << f.c_ << " " << f.d_ << " ";    
    }
};

int main() 
{
    constexpr Foo f = Foo{}.SetB(5).SetA(2);
    std::cout << f;
}

Live Example using Clang 3.4 SVN trunk with std=c++1y . 使用Clang 3.4 SVN trunk的实例std=c++1y

I'm not sure if classes with 30 parameters are a good idea (Single Responsiblity Principle and all that) but at least the above code scales linearly in the number of setters, with only 1 argument per setter. 我不确定具有30个参数的类是否是一个好主意(Single Responsiblity Principle和所有这些),但至少上面的代码在setter的数量上线性缩放,每个setter只有1个参数。 Note also that there are only 2 constructors: the default one (which takes its arguments from the in-class initializers) and the full one which takes 30 ints in your ultimate case). 还要注意,只有2个构造函数:默认的构造函数(从类内初始化程序中获取其参数)和在最终情况下需要30个整数的完整构造函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM