c++c-preprocessorbazelconditional-compilationcompiler-flags

Instantiate different classes according to different compilation flag of same header file in c++


I have a class that varies according to a compilation flag. I would like to instantiate both the versions of the class in the same scope.

my_class.h

#ifndef MY_CLASS_H
#define MY_CLASS_H

#ifdef COMP_FLAG
#define MY_CLASS MyClassFlagged
#else
#define MY_CLASS MyClassNotFlagged
#endif

class MY_CLASS
{
};

#endif

Then, I have created two header files: my_class_flagged.h and my_class_not_flagged.h

my_class_flagged.h

#ifndef MY_CLASS_FLAGGED
#define MY_CLASS_FLAGGED

#include "my_class.h"

#endif
my_class_not_flagged.h

#ifndef MY_CLASS_NOT_FLAGGED
#define MY_CLASS_NOT_FLAGGED

#include "my_class.h"

#endif

I am using bazel as build system, so I have compiled them with COMP_FLAG and without it to create two separate targets.

BUILD

cc_library(
    name = "not_flagged",
    hdrs = ["my_class.h"],
)

cc_library(
    name = "flagged",
    hdrs = ["my_class.h"],
    defines = ["COMP_FLAG"],
)

cc_library(
    name = "my_class_flagged",
    hdrs = ["my_class_flagged.h"],
    deps = [":flagged"],
)

cc_library(
    name = "my_class_not_flagged",
    hdrs = ["my_class_not_flagged.h"],
    deps = [":not_flagged"],
)

Then I am not able to instantiate both of them but only one:

#include "my_class_flagged.h"
#include "my_class_not_flagged.h"

// NOT BUILD
TEST(MyClassNotFlaggedTest, NotFlaggedInstantiation)
{
    MyClassNotFlagged my_class_not_flagged;
    std::ignore = my_class_not_flagged;
}

// PASS
TEST(MyClassFlaggedTest, FlaggedInstantiation)
{
    MyClassFlagged my_class_flagged;
    std::ignore = my_class_flagged;
}

obtaining this error: error: 'MyClassNotFlagged' was not declared in this scope; did you mean 'MyClassFlagged'?

where I have used:

cc_test(
    name = "test",
    srcs = ["test.cpp"],
    deps = [
        ":my_class_flagged",
        ":my_class_not_flagged",
    ],
)

Solution Attempts

[463035818_is_not_an_ai] Problem with include guards

I have tried to modify my_class.h file:

#ifdef COMP_FLAG
#ifndef MY_CLASS_FLAGGED_H
#define MY_CLASS_FLAGGED_H

#define MY_CLASS MyClassFlagged

class MY_CLASS
{
};

#endif  // MY_CLASS_FLAGGED_H
#else
#ifndef MY_CLASS_NOT_FLAGGED_H
#define MY_CLASS_NOT_FLAGGED_H

#define MY_CLASS MyClassNotFlagged

class MY_CLASS
{
};

#endif  // MY_CLASS_NOT_FLAGGED_H
#endif

However, now I got both test not building:

error: 'MyClassNotFlagged' was not declared in this scope

error: 'MyClassFlagged' was not declared in this scope.


Solution

  • I am afraid this isn't the answer, because from comments it is already apparent that your example is too much simplified. Anyhow, it appears you make something very simple into something complicated. Use the language when you can, only resort to conditional compilation using the preprocessor when needed. Nothing in your example really needs the preprocessor.

    You can define one class template:

    template <int tag> 
    struct MY_CLASS {};   
    
    using MyClassFlagged = MY_CLASS<0>;
    using MyClassNotFlagged = MY_CLASS<1>;
    

    There are ways to determine the difference between this approach and defining two separate (non-template) classes, but for most uses and applications that difference should not matter.


    If you want to stay with the preprocessor (for whatever reasons) then I suggest to at least leave the build system out. Include the header twice, define and undefine the flag explicitly in your code.

    In the my_class_flagged.h write:

    #ifndef MY_CLASS_FLAGGED
    #define MY_CLASS_FLAGGED
    
    #define COMP_FLAG
    #include "my_class.h"
    #undef COMP_FLAG
    
    #endif
    

    And in my_class_not_flagged.h write:

    #ifndef MY_CLASS_NOT_FLAGGED
    #define MY_CLASS_NOT_FLAGGED
    
    #include "my_class.h"
    
    #endif
    

    And remove the include guards from my_class.h. And I suggest to rename it to eg my_class.x or whatever you like different from .h to indicate it shall not be included directly.

    And, in my_class.h as the last line before closing the header guard, add:

    #undef MY_CLASS
    

    If you do that, you can include both headers (in any order) in main.cpp to have both classes defined.

    Live Demo