An idiom with a named compile-time parameter with constexpr

I recently came across quite a few situations where a Named Hierarchical Parameter would be useful, but I would like it to be guaranteed at compile time. The standard method for returning links in a chain almost always calls a run-time constructor (compilation with Clang 3.3-O3).

I was not able to find anything with a reference to this, so I tried to get this to work with constexpr and got something functional:

 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 good for a small number of parameters, for large numbers it quickly explodes 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 look at this with classes that have many (30+) parameters, and it looks like it will be error prone if it is expanded in the future.

EDIT: Remote C ++ 1y tag - while C ++ 1y really fixes the problem (thanks to TemplateRex!), This is for production code and we are stuck with C ++ 11. If that means it is not possible then I I suppose that's the way it is.

EDIT2: To show why I'm looking for this, here is a usage example. Currently, with our platform, developers need to explicitly set bit vectors for hardware configurations, and although this is normal, it is very error prone. Some use designated initializers from the C99 extension, which is good, but non-standard:

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

Most, however, do not even use this, but simply enter blob numbers. So, as an improvement, I would like to move on to something similar (since it also creates better code):

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

Which may be much more detailed, but much clearer when reading later.

+4
source share
2 answers

Using template metaprogramming

Here is what I came up with to solve your problem (at least partially). Using template metaprogramming, you can use the compiler to do most of the tasks for you. These methods look strange for those who have never seen such code before, but, fortunately, most of the complexity can be hidden in the header, and users interact with the library carefully and briefly.

Determination of the class of the sample and its use

Here is an example of what a class definition entails:

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

Class usage

 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 work is done using the const_obj template. It provides a mechanism for modifying your object at compile time. Just like Tuple , fields are accessed using indexes 0, but that doesn't stop you from wrapping setters with friendly names, as you did with SetPortNumber and SetPortLetter above. (They just go to Set <0> and Set <1>)

About repository

In the current implementation, after all setters have been called and the declared object, the fields are ultimately stored in a compact const unsigned char array named data in the base class. If you use fields that are not unsigned characters (as was done above using PortNumber, for example), the field is divided into a large endien unsigned char (it can be changed to a small endian if necessary). If you do not need the actual storage with the actual memory address, you can completely omit it by changing packed_storage (see the full implementation link below), and the values ​​will be available at compile time.

Limitations

This implementation allows using only integral types as fields (all tastes of shorts, ints, longs, bool, char). However, you can still provide setters that operate on more than one field. 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 implementing this small 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. It has not been tested for hours and hours, so of course there may be errors, but it should provide you with a good base to start with. Hope this helps!

+4
source

In C ++ 14, restrictions on the constexpr function will be relaxed, and the usual chain of reference return setters will work at compile time:

 #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 SVN 3.4 SVN trunk with std=c++1y .

I'm not sure that classes with 30 parameters are a good idea (Single Responsiblity Principle and all that), but at least the code above scales linearly in the number of setters, and only 1 argument per setter. Also note that there are only 2 constructors: the default (which takes its arguments from the initializers in the class) and the full, which takes 30 ints in your final case).

+3
source

All Articles