Zero runtime costs feature flags

  • A+
Category:Languages

The goal is to have a Feature flag system with no run-time costs. A simple C99 solution is:

C99:

#include <stdio.h>  #define flag_1 1  int main() { #if flag_1     printf("Active/n"); #else     printf("InActive/n"); #endif      return 0; } 

not that the C++ 17 solution here seems elegant:

#include <iostream>  constexpr  bool tag_flag_1 = true; constexpr  bool tag_flag_2 = true;   int main() {     if constexpr(tag_flag_1)     {         std::cout << "Active" << std::endl;     }     else     {         std::cout << "InActive" << std::endl;     }     return 0; } 

But is not working since the "if constexpr" construct is only valid where the "if" construct is. For instance this code is not valid:

if constexpr(tag_flag_1) {     class foo     {      }; } 

while this one is:

#if tag_flag_1     class foo     {      }; #endif 

Problem with the C99 solution:

typing:

if constexpr(flag_not_exists) 

will result in a compilation error, while:

#if flag_not_exists 

Will not.

Of course one could always write this alternative cumbersome solution in C99:

#include "stdio.h"  #define flag_1 0  int main() { #if flag_1     printf("Active/n"); #elif defined(flag_1)     printf("InActive/n"); #else #error undefined_flag #endif      return 0; } 

Question:

Is there an elegant way to ensure that the usage of a non-existant (mis-spelled for instance) feature flag result in a compilation error?

It is important for the solution:

  • Not to require the permanent discipline of an extra "else #error" from the developer. We all are lazy...
  • To have 0 run-time costs
  • To support Boolean operations "#if (feature_1 || feature_2) && !feature_3"
  • Precision of "Feature Flag System": When you develop a new feature, you can modify function signature, add a member to a class, change the type of a variable, add a new class... It is literally equivalent to having two branches (main and feature) living in the same file. Switching from one to the other by turning flag on and off. Any code modification can be feature flagged!

I'm very curious about a possible template and/or macro oriented solution.

Edit from comment question:

A simple solution with C99 would be nice. Currently our software compiles with a Cpp11 compiler. But even a Cpp17 solution would be nice for later... any solution is good, the more backward compatible the better (as more people could use it!).

 


I hope I completely understood the requirements. If not, please let me know and I'll edit or withdraw this answer.

The code belows (C++11) complies to the requirements the following way:

  • "Not to require the permanent discipline of an extra "else #error" from the developer. We all are lazy...": actually it is just needed once (static_assert()s that define the allowed features combinations).
  • "To have 0 run-time costs": yes, thanks to compiler's optimizer (if turned on).
  • "To support Boolean operations "#if (feature_1 || feature_2) && !feature_3"": yes, definitely, but not using preprocessing directives
  • "Precision of "Feature Flag System" [... see OP's question and comments]": not completely. There is no conditional compilation here so the whole code is always compiled and all types used in the runtime code have to be defined (even if they are a different way) regardless the features combinations. However, unused code is stripped out by the compiler's optimizer (if turned on).

This being said, this kind of solution complicates the code. The below can be useful in some precise parts of a software but I would not use it to handle the whole conditional activation of my code. I use such things in combination of plain branches and preprocessor directives usually. So please take the code below as a "little extremistic example".

#include <iostream>  // Having all your flags encapsulated in a class allows you to avoid errors tied to typos: // - "#if feaature_1" (notice the typo in 'feaature') would just exclude some code silentely // - but "if (FeatureFlags::feaature_1)" (same typo) produces a compile error, which is better class FeatureFlags { public:     static constexpr bool feature_1 = false; // This would also work with 'const' instead of 'constexpr' actually.     static constexpr bool feature_2 = true;     static constexpr bool feature_3 = true; };   // We want to define a conditional class Foo. But we can't just use FeatureFlags to do conditional compile, and  // we can't test FeatureFlags with preprocessor #directives either. So we split it as follow: // - There's one version of it just for FeatureFlags::feature_1 // - There's another for FeatureFlags::feature_3 provided FeatureFlags::feature_1 is not defined // - And there's a default one that deliberately cause a compile time error as we want //   either FeatureFlags::feature_1 or FeatureFlags::feature_3 to be activated, in this example.  // This pure virtual class is just there to cause compile-time errors should we forget to // implement a part of the class's behaviour in our Foo variants.  // This is not mandatory: if we don't use such an interface we'll just have compile-time errors later  // in the run-time code instead of having them at class definition level. // This doesn't cause performances issues as the compiler's optimizer will handle that for us, we'll see later. class Foo_Interface { public:     virtual void doSomething() = 0; };   // Will be stripped out by modern compilers' optimizers if FeatureFlags::feature_1 is false // Side note: Methods are implemented inline just to have a compact example to copy/paste.  // It would be best to have them in a separate .cpp file of course, as we usually do. class Foo_Feature1 : public Foo_Interface { public:     Foo_Feature1()         : i(5)     {}      virtual void doSomething()      {         std::cout << "Foo_Feature1::doSomething() with " << i << std::endl;     }  private:     int i; };   // Will be stripped out by modern compilers' optimizers if FeatureFlags::feature_1 is true or FeatureFlags::feature_3 is false class Foo_NotFeature1But3 : public Foo_Interface { public:     Foo_NotFeature1But3()         : d(1e-5)     {}      virtual void doSomething()     {         std::cout << "Foo_NotFeature1But3::doSomething() with " << d << std::endl;     }  private:     double d; };   // Will be stripped out by modern compilers' optimizers if FeatureFlags::feature_1 is true or FeatureFlags::feature_3 is true class Foo_Default { public:     Foo_Default()     {         // This produces an error at compile time should the activated features be unconsistant.         // static_assert(cdt,msg) can be used everywhere, not only in blocks. It could have been right under          // the definition of FeatureFlags for example. It really depends on where you would like the error to appear.         static_assert(FeatureFlags::feature_1 || FeatureFlags::feature_3, "We shouldn't be using Foo_Default, please enable at least feature 1 or 3");     }      virtual void doSomething()     {} };   // Now we can conditionnaly define Foo: // - Foo is Foo_Feature1 if FeatureFlags::feature_1 is true.  // - Otherwise, it is either Foo_NotFeature1But3 or Foo_Default depending on FeatureFlags::feature_3 typedef std::conditional <     FeatureFlags::feature_1,      Foo_Feature1,      std::conditional<FeatureFlags::feature_3, Foo_NotFeature1But3, Foo_Default>::type >::type Foo;   void main() {     // What follows is automatically inlined in release mode, no virtual table. Not even an object.     // If Foo becomes bigger or more complicated, this might change. But in that case this means the     // cost of the vtable becomes neglictible. All of this can perfectly be done with no inheritance at      // all though (see comments at Foo_Interface's definition)     Foo f;       f.doSomething();        if (FeatureFlags::feature_1)     {         // Do something or not depending on feature_1.     }      if (FeatureFlags::feature_2)     {         // Do something or not depending on feature_2.     }      if ((FeatureFlags::feature_1 || FeatureFlags::feature_2) && !FeatureFlags::feature_3)     {         // Why not, after all, but that sounds odd...     } } 

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: