Faking Static If in C ++

I am testing combinations of various optimizations, and for them I need statics - if, as described in http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Static-If-I-Had-a-Hammer to enable or disable certain optimizations, if (const-expr) does not always work, since some optimizations are related to changing the data layout, and this cannot be done in the function area.

Basically, I want:

template<bool enable_optimization> class Algo{ struct Foo{ int a; if(enable_optimization){ int b; } void bar(){ if(enable_optimization){ b = 0; } } }; }; 

(Yes, less memory to remove b from the data layout matters in my case.)

I am currently pretending to use a very bad hack. I am looking for a better way to do this.

Ah file

 #ifndef A_H #define A_H template<class enable_optimization> class Algo; #include "bh" #endif 

Bh file (this file is auto-generated from a Python script)

 #define ENABLE_OPTIMIZATION 0 #include "ch" #undef #define ENABLE_OPTIMIZATION 1 #include "ch" #undef 

Ch file

 template<> class Algo<ENABLE_OPTIMIZATION>{ struct Foo{ int a; #if ENABLE_OPTIMIZATION int b; #endif void bar(){ #if ENABLE_OPTIMIZATION b = 0; #endif } }; }; 

Does anyone know how best to do this? Theoretically, this can be done using meta-programming of the templates, and first I used it. At least the way I used it was a pain in the ass and led to a completely unreadable and bloated code. Using the hack above has led to a significant increase in productivity.

EDIT: I have several optimization flags, and they interact.

+8
c ++ optimization static if-statement
source share
3 answers

There is no reason why code should get complicated when using templates:

 template<bool enable_optimization> class FooOptimized { protected: int b; void bar_optim() { b = 0; } }; template<> class FooOptimized<false> { protected: void bar_optim() { } }; template<bool enable_optimization> struct Algo { struct Foo : FooOptimized<enable_optimization> { int a; void bar() { this->bar_optim(); } }; }; 

There is no need for metaprogramming, just separate the parts that change depending on whether optimization is included in the new type and specializes in it.

Since the new type is used as the base class when it is empty (i.e. there is no FooOptimized::b member), it will not take a space, therefore sizeof(Algo<false>::Foo) == sizeof(int) .


(Feel free to ignore the rest of this answer, it does not address the issue directly, instead it offers a different approach that has different trade-offs. Whether it is β€œbetter” or not, the actual code that is not shown in a trivial example depends entirely on the details. asked in the question.)

As a related but separate issue, parts of Algo and Algo::Foo that do not depend on whether optimization is turned on still depend on the template parameter, therefore, although you only write these bits of code once, the compiler will generate two sets of object codes. Depending on how much work is in this code and how it is used, you may find that there is an advantage to changing this to code without a template, i.e. replacing static polymorphism with dynamic polymorphism. For example, you can make the enable_optimization flag enable_optimization argument to the runtime constructor instead of a template argument:

 struct FooImpl { virtual void bar() { } }; class FooOptimized : FooImpl { int b; void bar() { b = 0; } }; struct Algo { class Foo { std::unique_ptr<FooImpl> impl; public: explicit Foo(bool optimize) : impl(optimize ? new FooOptimized : new FooImpl) { } int a; void bar() { impl->bar(); } }; }; 

You will need to profile and test it to determine if a virtual function has less cost than duplicating code in Algo and Algo::Foo , which is independent of the template parameter.

+6
source share

Note: however, this approach does not work, since there seems to be no way to have a member in the class without allocating space for this element. If you have an idea how to make it work, feel free to edit.

You can use the idiom as follows:

 template<bool optimized, typename T> struct dbg_obj { struct type { // dummy operations so your code will compile using this type instead of T template<typename U> type& operator=(const U&) { return *this; } operator T&() { return *static_cast<T*>(0); /* this should never be executed! */ } }; }; template<typename T> struct dbg_obj<false, T> { typedef T type; }; template<bool enable_optimization> class Algo{ struct Foo{ int a; typename dbg_obj<enable_optimization, int>::type b; void bar(){ if(enable_optimization){ b = 0; } } }; }; 

If optimization is disabled, this gives you a normal int member. If optimization is enabled, then type b is a memberless structure that does not take up space.

How does your bar method use what looks like an if runtime to decide whether to access b or not, unlike a clear compile-time mechanism, such as specializing templates, all the operations that you use on b should be accessible from fictitious structure. Even if the relevant sections are never executed, and the compiler is most likely to optimize them, you must first check the correctness. Thus, the string b = 0 must also be compiled for the replacement type. This is the reason for the fictitious job and fictitious operations. Although it would be enough for your code, I have included both options if they prove useful at some other point and give you an idea of ​​how to add more if you ever need them.

+1
source share

The static solution presented by Jonathan Wackley (not dynamic) is the way to go.

The idea is simple:

  • Isolate "specific" data into its own class (template and specialized, respectively)
  • All calls to this data are completely passed to the specialized class (the interface must be uniform)

Then, in order not to incur overhead, you use EBO (Empty Base Optimization) to your advantage, inheriting either the specialization of this special class.

Note: to get at least 1 byte an attribute is required, the base class is not in some conditions (for example, it is empty).

In your case:

  • b is specific data
  • there is one operation associated with this (consisting in setting its value)

Thus, we can easily build a class:

 template <typename T> struct FooDependent; template <> struct FooDependent<true> { int b; void set(int x) { b = x; } }; template <> struct FooDependent<false> { void set(int) {} }; 

And then we can enter it in Foo , using EBO in our interests:

 struct Foo: FooDependent<enable_optimization> { int a; void bar() { this->set(0); } }; 

Note: use this in the template to access the elements of the base classes, or good compilers will reject your code.

0
source share

All Articles