简体   繁体   中英

Template Specialization and DLL's: Visual Studio vs. (GCC / Clang)

This problem is complex to describe. I have a cross-platform application. Currently I am using Visual Studio 2012, GCC 4.8.x, and Clang 3.5. The application consists of many dynamic libraries so pieces of the problem are spread through various assemblies.

I have looked at these stack overflow questions, but they do not seem to address this issue:

  1. Clang can't handle a template specialization using referenced template template, but GCC can
  2. template specialization, different behavior on windows vs gcc?
  3. Template specialization and instantiation
  4. Template specialization and enable_if problems
  5. Explicit template specialization in g++ causing troubles

I have a small army of templated functions each with a unique specialization (using type traits, mostly). These templated functions take types and convert them to special strings ( std::string ToString(T x) ) and take such strings and convert them back to their types ( T StringTo<T>(std::string x) ). I have other templated classes that use these special conversions for their types.

Some dynamic libraries (plugins) may want to add new types and conversions for them. Great. To help with this, I wrote an evil macro (though the problem exists without the macro at all, just full disclosure here.)

The Problem

When compiling on Visual Studio 2012, everything works great. Switch over to GCC/Clang, and things start to go bad. If I keep the implementation identical, I get a problem where the compiler cannot find a matching overload for the new type. I have tried to #ifdef WIN32 my way out of this, but no luck. I cannot find a path that gets me the new templated overloads.

I suspect this might be because GCC/Clang does not deal with template specializations being spread among libraries well (and this seems like a reasonably difficult problem for compiler writers)...but I do not want to accept defeat. I even tried setting compiler options on Clang (-fms-compatibility and -fdelayed-template-parsing) with no success. ( http://clang.llvm.org/docs/MSVCCompatibility.html )

Compiler Output

Here is a sample of the compiler output, though names were changed to protect the innocent and relate to the pseudo-code below. There are many many variations of this message (basically one per specialization):

/path/utilities/ToString.h:1014:15: note:   template argument deduction/substitution failed:
/path/utilities/ToString.h: In substitution of ‘template<class T> std::string ToString(const T&, typename std::enable_if<((std::is_same<T, char>::value || std::is_same<T, signed char>::value) || std::is_same<T, unsigned char>::value), T>::type*) [with T = Bar]’:
/path/core/Foo.h:156:49:   required from ‘std::string Foo<T>::getValue() const [with T = Bar; std::string = std::basic_string<char>]’
/path/plugin/Bar.cpp:201:1:   required from here
/path/utilities/ToString.h:1014:15: error: no type named ‘type’ in ‘struct std::enable_if<false, Bar>’
/path/core/Foo.h: In instantiation of ‘std::string Foo<T>::getValue() const [with T = Bar; std::string = std::basic_string<char>]’:
/path/plugin/Bar.cpp:201:1:   required from here
/path/utilities/ToString.h:1029:15: note: template<class T> std::string ToString(const T&, typename std::enable_if<(((std::is_convertible<T*, std::basic_string<char> >::value && (! std::is_same<T, char>::value)) && (! std::is_same<T, signed char>::value)) && (! std::is_same<T, unsigned char>::value)), T>::type*)
   std::string ToString(const T& x, typename std::enable_if<std::is_convertible<T*, std::string>::value && !std::is_same<T, char>::value && !std::is_same<T, signed char>::value && !std::is_same<T, unsigned char>::value, T>::type*)
               ^

Pseudo Code

I have gone through many gyrations trying to move definitions around, separate template definitions from declarations, compiler flags...it's all a horrible blur.

Here is some code to demonstrate what I am doing:

Utilities.dll, ToString.h

// Prototype some templates (Makes GCC/Clang Happy.  Visual Studio did not require.)
template<typename T> typename std::enable_if<std::is_constructible<T, std::string>::value, T>::type StringTo(std::string x);
template<typename T> typename std::enable_if<std::is_same<T, float>::value, T>::type StringTo(std::string x);
template<typename T> typename std::enable_if<std::is_same<T, double>::value, T>::type StringTo(std::string x);

// ...and many more.

// Implement the templates
template<typename T> 
typename std::enable_if<std::is_constructible<T, std::string>::value, T>::type StringTo(std::string x)
{
  return T(x);
}

// ...and many more.

// Finally, define a macro to build more conversions from types later.
#define BUILD_MORE(V) \
  template<typename T>\
  typename std::enable_if<std::is_same<T, V>::value, T>::type StringTo(std::string x)\
  {\
    return specialConversionCodeHere;\
  }

Core.dll (dependent on Utilities.dll), Foo.h

  template<typename T>
  class Foo
  {
    T getValue()
    {
      // Use our specialized template.
      return StringTo<T>(this->value);
    }

    std::string value;
  }

Plugin.dll (dependent on Core.dll), Bar.cpp

  // Define a new class.
  class Bar...

  // Now use the fancy macro to build a StringTo conversion for it.
  BUILD_MORE(Bar)

  // Now use said conversion
  void foobar()
  {
    // Create the templated class (Foo) which uses a 
    // specialized template for class Bar conversion from string.
    // Prepare for horrible build errors outside of Visual Studio
    auto fb = new Foo<Bar>();
    auto x = fb->getValue(); 
  }

I added a github project that demonstrates the problem: https://github.com/DigitalInBlue/TemplateTest

The issue here is that MSVC and GCC/Clang do resolve the templates differently. Look here at the post marked "HUGE EDIT": Overload Resolution in a Namespace

I posted an example project on GitHub that illustrates the problem. By changing the order that template overloads are defined in the final executable vs. included, the error is resolved on all the compilers I tested on. Essentially:

// First
#include <original set of templates and specializations>

// Next
// Define new specialization.

// Then
#include <class that uses the specialization, possibly from another assembly>

// Finally
// Code that uses the #include'ed class that uses the specialization.

I will update the github project to show the "before" and "after"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM