Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Faking Static If in C++

I am testing combinations of various optimizations and for these I need a static-if as described in http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Static-If-I-Had-a-Hammer to enable and disable specific optimizations. if(const-expr) does not always work as some optimizations involve changing the data layout and this can not be done at function scope.

Basically what I want is this:

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

    void bar(){
      if(enable_optimization){
        b = 0;
      }
    }
  };
};

(Yes, the smaller memory footprint of removing b from the data layout is relevant in my case.)

Currently I am faking it using a very bad hack. I am looking for a better way of doing this.

File a.h

#ifndef A_H
#define A_H
template<class enable_optimization>
class Algo;
#include "b.h"
#endif

File b.h (this file is autogenerated from a Python script)

#define ENABLE_OPTIMIZATION 0
#include "c.h"
#undef
#define ENABLE_OPTIMIZATION 1
#include "c.h"
#undef

File c.h

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 of a better way of doing this? Theoretically it can be done using template meta programming and at first I used it. At least the way I used it was a pain in the ass and lead to completely unreadable and bloated code. Using the hack above resulted in a significant productivity boost.

EDIT: I have several optimization flags and these interact.

like image 604
B.S. Avatar asked Jul 21 '12 13:07

B.S.


2 Answers

There's no reason the code should get much more complicated 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();
      }
    };
  };

No metaprogramming needed, just separate the parts that vary based on whether optimisation is enabled into a new type and specialize it.

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


(Feel free to ignore the rest of this answer, it doesn't address the question directly, instead it suggest a different approach that has different trade-offs. Whether it is "better" or not depends entirely on the details of the real code, which are not shown in the trivial example given in the question.)

As a related, but separate issue, the parts of Algo and Algo::Foo that don't depend on whether optimization is enabled are still dependent on the template parameter, so although you only write those bits of code once, the compiler will generate two sets of object code. Depending how much work is in that code and how it's used you might find there is an advantage to changing that into non-template code, i.e. replacing static polymorphism with dynamic polymorphism. For example, you could make the enable_optimization flag a runtime constructor argument instead of 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'd have to profile and test it to determine whether the virtual function has more of less overhead than the duplication of code in Algo and Algo::Foo that doesn't depend on the template parameter.

like image 139
Jonathan Wakely Avatar answered Nov 15 '22 16:11

Jonathan Wakely


Note: as it stands, this approach doesn't work, as there appears to be no way to have a member in a class without allocating space for that member. If you have an idea how to make it work, feel free to edit.

You could use an idiom like this:

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 the type of b is a struct without members, which will not take any space.

As your method bar uses what looks like a runtime if to decide whether or not to access b, as opposed to a clear compile-time mechanism like template specialization, all the operations you use on b must be available from the dummy struct as well. Even if the relevant sections will never get executed, and the compiler most likely optimizes them away, the correctness checks come first. So the line b = 0 must compile for the replacement type as well. That's the reason for the dummy assignment and the dummy cast operations. Although either would suffice for your code, I included both in case they prove useful at some other point, and to give you an idea how to add more should you ever require them.

like image 45
5 revs Avatar answered Nov 15 '22 16:11

5 revs