1

I've tried creating a static library with two copies of the same function, defined in two seperate header/source files. These 2 source files should be mutually exclusive and not includable within the same file as each other. I've recreated the issue with the example code below. This was written and tested on VS2019, I haven't tested this on any other compiler yet.

The following is in a foobar static lib:

foo.h

#pragma once

#ifdef BAR
static_assert(-1, "Bar is already included, you shouldn't include both foo & bar");
#endif //BAR

#define FOO

void foobar();

foo.cpp

#include "foo.h"

#include <iostream>

void foobar(){
    std::cout << "this is foo!";
}

bar.h

#pragma once

#ifdef FOO
static_assert(-1, "Foo is already included, you shouldn't include both foo & bar");
#endif //FOO

#define BAR

void foobar();

bar.cpp

#include "bar.h"

#include <iostream>

void foobar(){
    std::cout << "this is bar!";
}

I could then create an executable project with a single main.cpp

I've created 3 diffirent versions of this file, but they all print the same line "this is foo!" Below are the 3 versions of the main file:

#include "bar.h"

int main() {
    foobar();
}
#include "foo.h"

int main() {
    foobar();
}
#include "foo.h"
#include "bar.h"

int main() {
    foobar();
}

So my questions are:

  • Why does the executable always print "this is foo!", no matter what it includes?
  • Why doesn't compilation fail when foo.h & bar.h are included in the same source file despite the static_assert in the header files?
  • Is there a way to achieve 2 definitoins of the same function within the same library, but not allow them to be called at the same time?

Edit:

Thank you very much @selbie, you're answer is very useful. I've rewritten it so that it's slightly less hacky.

foo.h

#pragma once

#ifdef BAR
static_assert(false, "Bar is already included, you shouldn't include both foo & bar");

#endif //BAR

#define FOO
#include "foobar.h"

bar.h

#pragma once

#ifdef FOO
static_assert(false, "Foo is already included, you shouldn't include both foo & bar");

#endif //FOO

#define BAR
#include "foobar.h"

foobar.h

#pragma once

void foobar_foo();
void foobar_bar();

inline void foobar() {
#ifdef FOO
    foobar_foo();
#endif // FOO
#ifdef BAR
    foobar_bar();
#endif // BAR
}

foobar.cpp

#include "foobar.h"

#include <iostream>

void foobar_foo() {
    std::cout << "this is foo!";
}

void foobar_bar() {
    std::cout << "this is bar!";
}
  • 3
    Even if a static assert was included, it isn't violated. `-1` is truthy. – StoryTeller - Unslander Monica Dec 30 '21 at 23:19
  • you could also simply use `#error` – tromgy Dec 30 '21 at 23:21
  • You should not create two functions with the same signature in the same object file. You need to distinguish between compiling the source files and linking the resulting object files. Also, when both header files defines the same prototype, you only need one include file. And you should only ask one question - create a post for each question. –  Dec 30 '21 at 23:24
  • The shown code, as described, violates the One Definition Rule. C++ does not require a compiler diagnostic for an ODR violation, as described in the linked answer, and there are no guarantees, whatsoever, what the end result will be. – Sam Varshavchik Dec 30 '21 at 23:32
  • 1
    shouldn't your static_assert be passing `false` instead of `-1` to force a compile failure? – selbie Dec 30 '21 at 23:43
  • You can keep the two routines separated by putting them in different namespaces. – Eljay Dec 31 '21 at 00:00

1 Answers1

4

I'm surprised the linker didn't give you a warning about this. When it links together the final executable program, it's going to pick one of the foobar implementations and discard the other.

Pre-processor hack. foobar is defined as a macro or inline function that invokes a unique function name. Define foo.h and foo.cpp like the following below:

foo.h

#pragma once

#ifdef BAR
static_assert(false, "Bar is already included, you shouldn't include both foo & bar");

#endif //BAR

#define FOO

void foobar_foo();

#ifndef FOOBAR_IMPL
inline void foobar() {foobar_foo();}
#endif

// OR
// #ifdef foobar
// #undef foobar
// #endif
// #define foobar foobar_foo

Then foo.cpp:

#define FOOBAR_IMPL // avoids the ODR issue that user17732522 called out in comments

#include "foo.h"

#include <iostream>

void foobar_foo(){
    std::cout << "this is foo!";
}

Repeat this pattern for bar.h and bar.cpp.

selbie
  • 100,020
  • 15
  • 103
  • 173
  • Look closely - the `foobar` function only gets defined once - inside the foo.h header file or the bar.h header file, but not both. – selbie Dec 30 '21 at 23:31
  • foo.h declares a function called foobar_foo and and an inline called foobar that invokes foobar_foo. bar.h declares a function called foobar_bar and an inline called foobar that invokes foobar_bar. Since foo.h and bar.h can't be included in the same compilation, only one definition gets made. – selbie Dec 30 '21 at 23:39
  • @user17732522 - another macro hack to the rescue. Regardless, this entire solution is very gross. My bigger advice to the OP is to find a better way to change runtime behavior at compile time without resorting to macro hacks. – selbie Dec 30 '21 at 23:46
  • 1
    Yes, now I don't see any issue anymore. But it certainly is a hack. – user17732522 Dec 30 '21 at 23:52
  • Thank you very much, this works. I'm not sure I completley understand how the `FOOBAR_IMPL ` fixes the ODR rule, isn't the function still getting defined twice? – Hasan Al-Baghdadi Dec 31 '21 at 09:48
  • Also `void foobar_foo();` should be declared before the definition of `void foobar()`, otherwise `foobar_foo` would be undeclared when referenced. – Hasan Al-Baghdadi Dec 31 '21 at 09:50