Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linking errors when used in multiple places #75

Closed
12AT7 opened this issue Apr 11, 2020 · 3 comments
Closed

Linking errors when used in multiple places #75

12AT7 opened this issue Apr 11, 2020 · 3 comments

Comments

@12AT7
Copy link

12AT7 commented Apr 11, 2020

I have encountered a linker error that I do not fully understand, and a workaround which I do not fully understand either.

I am using a pattern where I parse a global ArgumentParser in main() (ish) and then pass around my options (by non-const reference) to multiple translation units where the different parts of the program know what their option keys are and can retrieve their specialized values. This pattern requires including argparse.hpp in multiple source files so the definition of ArgumentParser is available in each place it is used. I am compiling with clang 9.0.1.

Everything compiles great this way, but fails to link. The problem is that all the template specializations, such as

 template <> constexpr bool standard_signed_integer<signed char> = true;

are multiply defined. Clang only tells me where they are initially defined (exactly one time) but also errors out because they are multiply defined. OK, then:

/usr/bin/ld: src/tickertape/CMakeFiles/tickertape.dir/timescaledb.cpp.o:(.rodata+0x8): multiple definition of `argparse::details::standard_unsigned_integer<unsigned long>'; src/tickertape/CMakeFiles/tickertape.dir/tickertape.cpp.o:(.rodata+0x8): first defined here

I find this confusing because I would have thought that constexpr would be, well, const, and perhaps not instantiated multiple times in a way that could not be resolved at link. I am no constexpr expert though, and clearly something else is happening.

I tried using inline:

inline template <> constexpr bool standard_signed_integer<signed char> = true;

which, to my constexpr-standard-naive mind looks silly, and indeed this had no effect.
I also tried reducing this to a declaration and defining the specialization exactly once (in a '.cpp' file):

template <> constexpr bool standard_signed_integer<signed char>;

which did not compile. Finally, I found that this guy is onto something: https://stackoverflow.com/a/47982008
who suggested to put these template specializations in an "unnamed namespace", which I think means that each object file gets their own copy and these have distinct names at link (avoiding the duplicate symbols). This worked.

So this is what I think:

  1. I don't know enough C++ generally, especially with regard to constexpr.
  2. argparse would ideally support being used in multiple object files linked together without too much hassle. I would actually prefer have to link argparse and lose the header-only benefits than hack around this linkage problem, but I suspect that we can have both features if we just get this constexpr thing right.

So thanks for the great software, but where am I messing this up?

@p-ranav
Copy link
Owner

p-ranav commented Apr 12, 2020

Hello,

Thanks for this bug report. Placing these definitions in an anonymous/unnamed namespace makes sense because definition is treated as a definition of a namespace with unique name.

Unnamed namespaces as well as all namespaces declared directly or indirectly within an unnamed namespace have internal linkage. Since C++11, even though names in an unnamed namespace may be declared with external linkage, they are never accessible from other translation units because their namespace name is unique.

namespace {
    int i;  // defines ::(unique)::i
}
void f() {
    i++;  // increments ::(unique)::i
}
 
namespace A {
    namespace {
        int i; // A::(unique)::i
        int j; // A::(unique)::j
    }
    void g() { i++; } // A::unique::i++
}
 
using namespace A; // introduces all names from A into global namespace
void h() {
    i++;    // error: ::(unique)::i and ::A::(unique)::i are both in scope
    A::i++; // ok, increments ::A::(unique)::i
    j++;    // ok, increments ::A::(unique)::j
}

I've pushed the suggested fix. Please close if if the changes meet your needs.

@12AT7
Copy link
Author

12AT7 commented Apr 16, 2020

Cool, this works for me. I understand tactically how the fix works. I suppose I can even understand why this is needed generally. In this case, where the template variables are always the same value, I was surprised that the linker could not resolve the symbols like it does when two linked libraries both themselves link to a common symbol. But I suppose that had the constexpr been a more complicated program such that some units defined them false instead of true, then I suppose it stands to reason that they have to be unique symbols.

@12AT7 12AT7 closed this as completed Apr 16, 2020
@12AT7
Copy link
Author

12AT7 commented Apr 16, 2020

And, of course, thank you for the software, the careful response, and the rapid fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants