模板专业化和 DLL:Visual Studio 与 (GCC / Clang)

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

这个问题很难描述。我有一个跨平台的应用程序。目前我正在使用 Visual Studio 2012、GCC 4.8.x 和 Clang 3.5。该应用程序包含许多动态库,因此问题的各个部分分布在各种程序集中。

我看过这些堆栈溢出问题,但它们似乎没有解决这个问题:

  1. Clang can't handle a template specialization using referenced template template, but GCC can
  2. template specialization, different behavior on windows vs gcc?

我有一小群模板函数,每个函数都有独特的专业化(主要使用类型特征)。这些模板化函数采用类型并将它们转换为特殊字符串 (std::string ToString(T x)),然后采用此类字符串并将它们转换回它们的类型 (T StringTo<T>(std::string x))。我还有其他模板化的 类 将这些特殊转换用于它们的类型。

一些动态库(插件)可能想要为它们添加新的类型和转换。伟大的。为了解决这个问题,我写了一个邪恶的宏(尽管根本没有宏就存在问题,只是在这里完全公开。)

问题

在 Visual Studio 2012 上编译时,一切正常。 切换到 GCC/Clang,事情开始变糟。如果我保持相同的实现,我会遇到一个问题,即编译器无法为新类型找到匹配的重载。我曾尝试通过 #ifdef WIN32 解决此问题,但没有成功。我找不到让我获得新的模板化重载的路径。

我怀疑这可能是因为 GCC/Clang 没有很好地处理在库之间传播的模板专业化(这对编译器编写者来说似乎是一个相当困难的问题)......但我不想接受打败。我什至尝试在 Clang 上设置编译器选项(-fms-compatibility 和 -fdelayed-template-parsing)但没有成功。 (http://clang.llvm.org/docs/MSVCCompatibility.html)

编译器输出

这里是编译器输出的示例,但名称已更改以保护无辜者并与下面的伪代码相关。此消息有很多变体(基本上每个专业一个):

/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*)
               ^

伪代码

我经历了很多次尝试移动定义、将模板定义与声明、编译器标志分开……这一切都是可怕的模糊。

下面是一些代码来演示我在做什么:

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(依赖于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(依赖于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(); 
  }

我添加了一个 github 项目来演示问题:https://github.com/DigitalInBlue/TemplateTest

这里的问题是 MSVC 和 GCC/Clang 解析模板的方式不同。在此处查看标记为 "HUGE EDIT" 的 post:Overload Resolution in a Namespace

我 post 在 GitHub 上编辑了一个示例项目来说明问题。通过更改在最终可执行文件中定义模板重载与包含的顺序,错误在我测试的所有编译器上都得到了解决。本质上:

// 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.

我将更新 github 项目以显示 "before" 和 "after"