1

Apologies for not providing simple runnable failure code. The error is part of the larger codebase that would require a lot of refactoring.

I'm running into a very weird linking problem with my code that so far I can't solve. I have a class with static constexpr const char * for some strings and local std::sunique_ptr. The pointer is to a different templated class which contains another templated class (#2).

The main class is like this (abridged):

class Manager {
 public:
  Manager();

  virtual ~Manager();

 private:
  // Topic Constants
  static constexpr const char* kActuatorsCommand = "ActuatorsCommand";
  static constexpr const char* kActuatorsProxy = "ActuatorsProxy";

  std::unique_ptr<DataReader> faults_;
};

So the DataReader constractor takes two const string & parameters.

If I declare faults_ as a regular old pointer and create it with new the code runs and links just fine: DataReader *faults_ = new DataReader<uint32_t>(kActuatorsCommand, kActuatorsProxy).

However, if I use std::make_unique the linker complains that there is an undefined reference to those static const char* strings, even though they are in the header of the class.

Also, If I remove the #2 class everything links fine.

Using gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0


I understand it may be rather vague question, but some direction where to look would be appreciated.

Also, this question may be similar to this one. However, in my case everything is on one binary.


Update: finally found how to reproduce it.

class DataReader {
 public:

  explicit DataReader(const std::string& topic, const std::string& library_name)
      : topic_(topic),
        library_name_(library_name) {
  }

 private:
  const std::string name_;
  const std::string topic_;
  const std::string library_name_;
};


#include <memory>
#include "DataReader.h"

class Manager {
 public:
  Manager();
  virtual ~Manager();

 private:
  // Topic Constants
  static constexpr const char* kActuatorsCommand = "ActuatorsCommand";
  static constexpr const char* kActuatorsProxy = "ActuatorsProxy";

  std::unique_ptr<DataReader> faults_;
};


Manager::Manager() {
    faults_ = std::make_unique<DataReader>(kActuatorsCommand, kActuatorsProxy);
}

Manager::~Manager() {}

The code fails to link when compiled with -o0. With -03 it links fine.

g++ -O0 -Wall -Wconversion -lstdc++ -pthread -std=c++14 -o ex3 src/ex3.cpp
/tmp/ccJebZ18.o: In function `Manager::Manager()':
ex3.cpp:(.text+0x41): undefined reference to `Manager::kActuatorsProxy'
ex3.cpp:(.text+0x48): undefined reference to `Manager::kActuatorsCommand'
collect2: error: ld returned 1 exit status
Makefile:8: recipe for target 'ex3' failed
ilya1725
  • 4,496
  • 7
  • 43
  • 68
  • It sounds like what you've described could just be made into a smaller [mcve]: you already have `Manager` with the `const char*`, and you've described `DataReader` as taking two `std::string& const` parameters, are you sure you can't just reproduce this with an extra few lines of code? – Tas Jan 09 '19 at 22:31
  • I tried. I didn't succeed yet. Something else is missing in my smaller example that is causing the large one to fail. The irony is - once I find it I will be able to fix the main problem. – ilya1725 Jan 09 '19 at 22:34
  • 1
    I forget if `constexpr` changes things in this case so not dupe closing. related/dupe: https://stackoverflow.com/questions/16284629/undefined-reference-to-static-variable-c – NathanOliver Jan 09 '19 at 22:35
  • 1
    The difference is `make_unique` takes its parameters by reference, which makes them odr-used; whereas `std::string` takes them by value. I suspect that either of these would work: `std::make_unique(kActuatorsCommand + 0, kActuatorsProxy + 0)` , `std::make_unique(std::string(kActuatorsCommand), std::string(kActuatorsProxy))` . The latter is essentially what happens when `DataReader` constructor is called directly. – Igor Tandetnik Jan 09 '19 at 23:38
  • 1
    @IgorTandetnik: indeed `std::string(kActuatorsCommand)` worked. I guess it creates the local copy of the static string and passes a reference to that to the constructor. Making the names just `static const char*` also worked. I just wonder why removing variable of class #2 from `DataReader` didn't fail linking. – ilya1725 Jan 09 '19 at 23:48
  • I think it has something to do with inlining `A constexpr specifier used in a function or static member variable (since C++17) declaration implies inline` When you compile the code with `-O3` or `-O0 -std=c++17` the static variable might be inlines which avoids the undefined reference. As to why you get an undefined reference in the first place I have no idea. – MaLarsson Jan 10 '19 at 15:39
  • @MaLarsson: it links fine in C++17. – ilya1725 Jan 10 '19 at 15:41

1 Answers1

0

I think it has something to do with inlining

A constexpr specifier used in a function or static member variable (since C++17) declaration implies inline

When you compile the code with -O3 or -O0 -std=c++17 the static variable might be inlines which avoids the undefined reference.

I did some digging and found:

You can take the address of a static member if (and only if) it has an out-of-class definition

From Bjarne Stroustrup's C++ faq. Adding the following code to your example outside of the class body made it compile using -std=c++14:

constexpr const char* Manager::kActuatorsCommand;
constexpr const char* Manager::kActuatorsProxy;
MaLarsson
  • 260
  • 3
  • 10