Search code examples
c++templatesmsys2

Template function multiple definition error


There is a huge cmake based C++ project developed in Linux. I am trying to compile it in Windows 10.

I am getting the following linking error when I am trying to build it using MSYS2 64bit, and VSCode.

[build] [ 98%] Building CXX object CMakeFiles/heca_input_exe.dir/utils/options/structures_from_cmdline.cc.obj
[build] [100%] Linking CXX executable heca_input_exe.exe
[build] C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: CMakeFiles\heca_input_exe.dir/objects.a(string_utils.cc.obj): in function `std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >& utils::split<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >&, std::vector<char, std::allocator<char> > const&, bool, bool)':
[build] C:/Users/pc/Documents/__protein design/___hydrogen bond/BioshellHydrogenBond/utils/string_utils.cc:62: multiple definition of `std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >& utils::split<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >&, std::vector<char, std::allocator<char> > const&, bool, bool)'; CMakeFiles\heca_input_exe.dir/objects.a(DataTable.cc.obj):C:/Users/pc/DOCUME~1/__protein design/___hydrogen bond/BioshellHydrogenBond/utils/string_utils.hh:140: first defined here

The split() function is declared and defined as follows:

//File: string_utils.hh
//
// ... ... ... ...
//
/** @brief Splits a string into tokens based on a given delimiter.
 *
 * @param str - the input string with tokens to be converted
 * @param delim - character to be trimmed off (a space by default)
 * @param if_trim_tokens - if true, tokens will be trimmed of white space characters
 * @return the newly created vector that holds the tokens extracted from the given string
 */
std::vector<std::string> split(const std::string &str, const std::vector<char> & delim = {' '}, const bool if_trim_tokens = true,const bool if_trim_line = true);

/** @brief Splits a string into a vector of tokens based on a given delimiter.
 *
 * @param input_str - input string
 * @param separator - separator (delimiter)
 * @param max - maximum number of tokens to be extracted
 * @param results - vector to insert the resulting tokens
 */
void split(const std::string &input_str, const std::string &separator, const int max, std::vector<std::string> &results);
//
// ... ... ... ...
//
/** @brief Splits a string into tokens based on a given delimiter and converts them into a generic type T
 *
 * @tparam T - the final type of the data that will be converted from string
 * @param s - the input string with tokens to be converted
 * @param tokens - the resulting (converted) tokens will be stored here
 * @param delim - character to be trimmed off (a space by default)
 * @param if_trim_tokens - if true, tokens will be trimmed of white space characters
 * @return the reference to the vector of converted data
 */
template<typename T>
std::vector<T> & split(const std::string &s, std::vector<T> &tokens, const std::vector<char> & delim = {' '},
    const bool if_trim_tokens = true,const bool if_trim_line = true) {

  std::string s_copy(s);
  trim(s_copy);

  // --- replace all delimiters with the first one
  for(int i=1;i<delim.size();++i)
    std::replace(s_copy.begin(), s_copy.end(), delim[i], delim[0]);

  std::stringstream ss(s_copy);
  std::string item;
  T data;
  while (std::getline(ss, item, delim[0])) {
    if (if_trim_tokens) trim(item);
    if (item.size() == 0) continue;
    if(item.find_first_not_of(' ') != std::string::npos) {
      std::stringstream ss2(item);
      ss2 >> data;
      tokens.push_back(data);
    }
  }

  return tokens;
}
//
// ... ... ...
//
//File: string_utils.cc
//
// ... ... ... ...
//
void split(const std::string &input_str, const std::string &separator, const int max, std::vector<std::string> &results) {

  std::string str(input_str);
  int i = 0;
  size_t found = str.find_first_of(separator);

  while (found != std::string::npos) {
    if (found > 0) results.push_back(str.substr(0, found));
    str = str.substr(found + 1);
    found = str.find_first_of(separator);
    if (max > -1 && ++i == max) break;
  }

  if (str.length() > 0) results.push_back(str);
}

template<>
std::vector<std::string> &split<std::string>(const std::string &s,
                                             std::vector<std::string> &tokens,
    const std::vector<char> & delim,
    const bool if_trim_tokens,const bool if_trim_line) {

  std::string s_copy(s);

  // --- replace all delimiters with the first one
  for(int i=1;i<delim.size();++i)
    std::replace(s_copy.begin(), s_copy.end(), delim[i], delim[0]);

  if (if_trim_line==true) trim(s_copy);
  std::stringstream ss(s_copy);
  std::string item;
  while (std::getline(ss, item, delim[0])) {
    if (if_trim_tokens) trim(item);
    if (item.size() == 0) continue;
    tokens.push_back(item);
  }

  return tokens;
}
//
// ... ... ... ...
//

I have two questions:

  1. How can I resolve this error?

  2. Why does this error not show up in Linux?


Solution

  • If a template is explicitly specialized, then before any use of the explicitly-specialized specialization there must be a declaration of the explicit specialization. Otherwise the program is IFNDR (ill-formed, no diagnostic required).

    Since the explicit specialization definition is only visible in the one .cc translation unit that you are showing, this requirement will be violated at least in other translation units including the header file.

    You need to add the explicit specialization declaration below the template definition:

    template<>
    std::vector<std::string> &split<std::string>(const std::string &,
                                                 std::vector<std::string>,
                                                 const std::vector<char> &,
                                                 bool, bool);
    

    That it works on a different system is coincidence, a usual behavior resulting from IFNDR ODR-type violations. In fact, if it did work, then I suspect that the compiler implicitly instantiated the template definition and used that instead of the explicit specialization at some call sites. The program will probably behave non-deterministically as if the explicit specialization definition was used sometimes or the template definition other times, depending on compiler/linker choices.


    I have to say however, that I am not sure why the explicit specialization exists at all. Seems weird that for T = std::string specifically, there needs to be a different behavior.