8

I'm writing a C++ application in which I want to let the developer choose what algorithm to use for a particular problem at compile time. Both algorithms are implemented as C++ classes that implement a common interface, and are drop-in replacements for each other. They both have a .h and a .cpp file, and reside in a subdirectory (let's call it impl/).

In my Makefile, I have something along the lines of this:

...
IMPL = default
...
binary: ... impl/$(IMPL).o
...
impl/%.o: impl/%.cpp impl-interface.h impl/%.h
...
%o: %.cpp ...
    $(CXX) $(CXXFLAGS) -DIMPL=$(IMPL) -c -o $@ $*.cpp

The idea is that the user should be able to type make binary IMPL=thatimpl.

In whatever files wants to use the algorithm the user has chosen, I then do:

IImpl o = new IMPL();

However, this requires me to include the header file for the chosen implementation. Unfortunately, C++ requires #include to be followed by either a "string", a <libfile>. You can also use a macro as suggested here, but it requires the argument to the macro to be a literal string. If I use:

#define QUOTEME(M)       #M
#define INCLUDE_FILE(M)  QUOTEME(impl/##M##.h)
#include INCLUDE_FILE(IMPL)

The compiler will try to include the literal string impl/IMPL.h, rather than expanding IMPL to whatever was passed to make and then to the compiler.

Any pointers on how I might achieve this would be very welcome!

Community
  • 1
  • 1
Jon Gjengset
  • 4,078
  • 3
  • 30
  • 43
  • 4
    Why cant you simply `#ifdef`/`#else` ? – user1781290 Jan 08 '14 at 13:26
  • 1
    Because there are more than two possible implementations, and I'd rather not have to enumerate them in the source code if I can avoid it. Also brings the issue of keeping the list of implementations in sync with the implementations available. – Jon Gjengset Jan 08 '14 at 13:28
  • You also resolve the at "link" time instead of compile time. That way different library can implement IMPL and you chose which one a link time. There is no need of any pragma in your code. – mb14 Jan 08 '14 at 13:28
  • I'm not sure I follow? I wouldn't be able to call `new IMPL()` in my code unless the header defining that particular implementation was included. – Jon Gjengset Jan 08 '14 at 13:30
  • 3
    You can have your makefile create an '.h' file that contains the actual include – user1781290 Jan 08 '14 at 13:30
  • @Joe: How would that be different to what I am currently doing with the Makefile? – Jon Gjengset Jan 08 '14 at 13:34
  • @user1781290: That's not a bad fallback solution at all, but it does make it slightly trickier to make Make track dependencies correctly. Might then always recompile files depending on IMPL.. – Jon Gjengset Jan 08 '14 at 13:34
  • @Jonhoo I'm no makefile expert, but I've seen this done in a project. I'll be leaving soon, but I can look it up later, if you're still in need – user1781290 Jan 08 '14 at 13:35
  • @user1781290: That would be great! I have no doubts it could be done, it will just require some extra Make-fu. – Jon Gjengset Jan 08 '14 at 13:37
  • 1
    Seems to be the same problem than this one: https://stackoverflow.com/questions/5873722 – leemes Jan 08 '14 at 13:38
  • So without a makefile, what you want is a "computed include". GCC supports it: http://tigcc.ticalc.org/doc/cpp.html#SEC8a but it doesn't seem to be standard behavior, at least in C. The article says "These rules are implementation-defined behavior according to the C standard." – leemes Jan 08 '14 at 13:42
  • @leemes: Yes, it's somewhat similar, but not entirely identical. The answer [linked to](http://stackoverflow.com/questions/1489932/c-preprocessor-and-concatenation/1489985) by that question might be of some help though. Unfortunately, I then receive the error: `pasting "impl" and "/" does not give a valid preprocessing token`.. – Jon Gjengset Jan 08 '14 at 13:47
  • Stringification should be the last operation. First concatenate tokens, then stringify it for inclusion. – leemes Jan 08 '14 at 13:48
  • @Jonhoo: The header containing function declarations, sure, but the actual implementation content should already reside in a separate TU. – Lightness Races in Orbit Jan 08 '14 at 15:51

3 Answers3

8

You just need to add an extra layer of indirection, because of the way the preprocessor works. This should do:

#define QUOTEME(x) QUOTEME_1(x)
#define QUOTEME_1(x) #x
#define INCLUDE_FILE(x) QUOTEME(impl/x.h)

#include INCLUDE_FILE(IMPL)

Live example

Jon Gjengset
  • 4,078
  • 3
  • 30
  • 43
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • And this shows that using GCC it works for inclusion, not only to make C-strings out of it: http://ideone.com/5195Fx But as I said in the comments above, this doesn't seem to be standard behavior. Someone with experience with the standard should elaborate on that. – leemes Jan 08 '14 at 13:51
  • This code gives me: `error: #include expects "FILENAME" or ` when compiling as c++11 on both GCC and clang? – Jon Gjengset Jan 08 '14 at 13:54
  • Also, I assume `/h` should be `.h`? – Jon Gjengset Jan 08 '14 at 13:55
  • 1
    In your question there is `/h`, which I assume was a typo ;) – leemes Jan 08 '14 at 13:55
  • Ah, sorry, my bad. Fixed. – Jon Gjengset Jan 08 '14 at 13:56
  • If I compile this with `g++ (Ubuntu/Linaro 4.7.2-5ubuntu1) 4.7.2` without *any* options (so it will default to C++03, but I also tried C++11), I get: `test.cpp:8:28: fatal error: impl/thatimpl.h: No such file or directory` Which compiler do you use? – leemes Jan 08 '14 at 13:58
  • `g++ (GCC) 4.8.2 20131219 (prerelease)` on Arch Linux. The error is there with and without options. – Jon Gjengset Jan 08 '14 at 14:01
  • This is very strange.. When I compile your test.cpp now, it works, but if I substitute back in the `#include` you removed, it breaks.. – Jon Gjengset Jan 08 '14 at 14:03
  • @Jonhoo It's perfectly valid to use a macro as the argument for `#include`, as long as the macro expands to a valid argument. Your error must be somewhere else. – Angew is no longer proud of SO Jan 08 '14 at 14:03
  • I need to learn to read before replying. Yes, the error is definitely somewhere in my real code. Your test.cpp works as advertised. Thank you! – Jon Gjengset Jan 08 '14 at 14:05
  • Apparently the compiler gets confused when you type "inlcude" instead of "include"... – Jon Gjengset Jan 08 '14 at 14:16
2

I see tree way to resolve your problem:

  • using #ifdef which seems to me the cleaner:

as follow:

#if !defined(IMPL_CHOICE)
# define  IMPL_1 // default to 1, or
//#error "You have to define IMPL_CHOICE"
#endif

#if IMPL_CHOICE == 1

# include "impl1.h"
// other stuff

#elif IMPL_CHOICE == 2

# include "impl1.h"
// other stuff

//elif IMPL_CHOICE == 3 // And so on

#else
# error "invalid IMPL_CHOICE"
#endif
  • The other way is to add correct -I include directive in your makefile

Assuming the name are identical but reside in different directory

Assuming the tree structure

src/common/*.{cpp,h}
   /implementation_1/header.h
   /implementation_2/header.h
   /implementation_3/header.h

So your normal include directory is only "src/"

So your #include look like

#include "implementation_1/header.h"

Now, if you add also (on condition) "src/implementation_1/", it becomes

#include "header.h"

or alternatively use -include (for gcc) as you may do for pch (pre compiled header).

  • Or create dynamically your header during the makefile process...
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • The first suggestion was briefly addressed in the comments to the question: I'd rather not do that as there are multiple implementations, and keeping the available implementations in sync with the `#ifdef`s would be a paint. As for the second, I don't see how adding `-I` would help in this instance? The last proposal has also been mentioned above, and is a decent fallback. It does mean my Makefile will have to grow more complex to handle the dependencies though.. – Jon Gjengset Jan 08 '14 at 13:51
  • @Jonhoo: solution with `-I` explained in more detail. – Jarod42 Jan 08 '14 at 14:06
0

You can also use Boost Preprocessor, especially the BOOST_PP_STRINGIZE macro:

#include <boost/preprocessor/stringize.hpp>

#define INCLUDE_FILE(X) BOOST_PP_STRINGIZE(impl/X.h)

// expands to: #include "impl/IMPL.h" where IMPL is given by the developer
#include INCLUDE_FILE(IMPL)

Live example here.

(cf. my answer to Generate include file name in a macro)

Community
  • 1
  • 1
BenC
  • 8,729
  • 3
  • 49
  • 68