0

Having two c++ programs. One is simple and the other is a little complex. But all have the same problem. The simple one has the following files. foo.h:

class foo{  
static const int array[3];  
};    
const int foo::array[3] = { 1, 2, 3 };     <-------- Here is the line causing the error.

foo.cc:

#include "foo.h"

main.cc:

#include "foo.h"

int main()
{
}

while compile and link with the following command:

clang++ -c *.cc -std=c++17
clang++ *.o -o a.out -std=c++17

it reports the following error:

main.o:(.rodata+0x0): multiple definition of `foo::array'
foo.o:(.rodata+0x0): first defined here
clang-14: error: linker command failed with exit code 1 (use -v to see invocation)

For the complex one, having the following files. t_a.h:

#pragma once
#include "t_b.h"

class TA : public TB,
                    public TIT<TB, TA> {
public:
  static const char* name() { return "TA"; }
};

t_b.h:

#pragma once
#include "t_r.h"

class TB {

  TI<TB> t_i() const { return t_i_; }

 private:
  template <typename T, typename U>
  friend class TIT;
  TI<TB> t_i_{TI<TB>::kUkT};

};

t_i.h:

#pragma once

#include <string>
#include <stdint.h>

template <typename BaseT>
class TR;

template <typename BaseT>
class TI {
 public:
  const std::string& name() const;

  int8_t id() const { return id_; }

  bool operator==(TI other) const { return id_ == other.id(); }
  bool operator!=(TI other) const { return id_ != other.id(); }

  static const TI kUkT;

 private:
  friend class TR<BaseT>;
  explicit TI(int8_t id) : id_(id) {}
  int8_t id_;
};

template <typename BaseT, typename DerivedT>
class TIT {
 public:
  static const TI<BaseT> kT;
  TIT() {
    static_cast<BaseT*>(static_cast<DerivedT*>(this))->t_i_ = kT;
  }
  static bool classof(const BaseT* obj) { return obj->t_i() == kT; }
};

template <typename BaseT>
TI<BaseT> RST(const std::string& t);

template <typename BaseT, typename DerivedT>
const TI<BaseT> TIT<BaseT, DerivedT>::kT =
    RST<BaseT>(DerivedT::name());            <-------- This block of code should cause a similar error, but it does not.

t_r.h:

#pragma once

#include <cassert>
#include <map>
#include <mutex>
#include <string>
#include <vector>

#include "t_i.h"

template <typename BaseT>
class TR {
 public:
  TR(const TR&) = delete;
  TR& operator=(const TR&) = delete;

  static TR& GI();

  TI<BaseT> RT(const std::string& t);
  const std::string& GTN(TI<BaseT> i) const;

 private:
  TR() = default;
  mutable std::mutex mutex_;
  std::vector<std::string> names_;
  std::map<std::string, int8_t> name_to_id_;
};

template <typename BaseT>
TR<BaseT>& TR<BaseT>::GI() {
  static TR<BaseT> r;
  return r;
}

template <typename BaseT>
TI<BaseT> TR<BaseT>::RT(const std::string& t) {
  std::lock_guard<std::mutex> guard(mutex_);
  assert(name_to_id_.find(t) == name_to_id_.end());
  assert(names_.size() < static_cast<decltype(names_.size())>(
                             std::numeric_limits<int8_t>::max()));
  int8_t id = static_cast<int8_t>(names_.size());
  names_.emplace_back(t);
  name_to_id_[t] = id;
  return TI<BaseT>(id);
}

template <typename BaseT>
const std::string& TR<BaseT>::GTN(
    TI<BaseT> info) const {
  std::lock_guard<std::mutex> guard(mutex_);
  int8_t id = info.id();
  assert(id >= 0);
  assert(static_cast<size_t>(id) < names_.size());
  return names_[id];
}

template <typename BaseT>
TI<BaseT> RST(const std::string& type) {
  return TR<BaseT>::GI().RT(type);
}

template <typename BaseT>
const std::string& TI<BaseT>::name() const {
  return TR<BaseT>::GI().GTN(*this);
}

template <typename BaseT>
const TI<BaseT> TI<BaseT>::kUkT =
    RST<BaseT>("Uk");

use_t_i_1.cc:


#include "t_a.h"

TIT<TB, TA> test_class_1;

use_t_i_2.cc:

#include "t_a.h"

int main() {
  TIT<TB, TA> test_class_2;
}

When compile and link by the following command:

clang++ -c *.cc -std=c++17
clang++ *.o -o a.out -std=c++17

No error occurs. What could be the cause why the same class of grammar mistake exists in both of the two programs while one reports error but the other one does not? Could anyone please explain this? Could anyone please does a small adjustment to the second complex program so it also errs with the same class of error? Thanks in advance.

piratesailor
  • 305
  • 5
  • 15
  • 2
    When the compiler, in the preprocessing stage, sees an `#include` directive, it replaces the directive with the contents of the included file. This means both foo.cc and main.cc define `const int foo::array[3] = { 1, 2, 3 };`, breaking the [One Definition Rule](https://en.cppreference.com/w/cpp/language/definition). – user4581301 Jan 13 '23 at 00:29
  • @user4581301 Thank you. Yes. But the second complex problem also has a block of code included by two cc files. And when linking them, it should report a similar error, but it does not. – piratesailor Jan 13 '23 at 00:31
  • The answer probably explains that bit. Until that template is actually used, nothing is defined. A cc file that includes the template and doesn't use it doesn't define anything. We need to see more in order to be certain the answer is right, but it probably is. – user4581301 Jan 13 '23 at 00:33
  • Since the variables are never used and I'm not seeing any observable behaviour it's entirely possible that the compiler said, " it. I'm not going to bother expanding those templates. I'm going to have to build this up and look at it closer. – user4581301 Jan 13 '23 at 05:17
  • After experimenting with a much simpler example, one header with one template with one static variable and two compiled source files and finding not only did they link, they linked to one variable, at least with GCC, I had the right keywords to find [Template static variable](https://stackoverflow.com/q/1553854/4581301). In other words you probably have a duplicate here, but I'm not willing to hammer the question shut because there may be more subtlety in your much more complex example. That said, what you're seeing will have its roots in litb's answer. – user4581301 Jan 13 '23 at 06:17

2 Answers2

1

this line

 const int foo::array[3] = { 1, 2, 3 }; 

needs to be in one .cc file, not in a header. Just pick one

pm100
  • 48,078
  • 23
  • 82
  • 145
1

C++ explicitly allows multiple definitions of templated entities:

basic.def.odr#13:

There can be more than one definition of a

  • class type ([class]),
  • enumeration type ([dcl.enum]),
  • inline function or variable ([dcl.inline]),
  • templated entity ([temp.pre]),
  • default argument for a parameter (for a function in a given scope) ([dcl.fct.default]), or
  • default template argument ([temp.param])

So it does not caused the multiple definition error.

fefe
  • 3,342
  • 2
  • 23
  • 45