使用GCC 5.4进行C++模板变量特化

3
我在一个项目的CI构建中遇到了一个奇怪的错误。这只发生在相对较老的操作系统(Ubuntu 16.04)和不太新的编译器(gcc 5.4.0)上。
导致错误的代码类似于以下内容:(https://godbolt.org/z/MhY6sv)
#include <limits>
#include <string>

#define DEFAULT_INIT(x) x{defaultValue<decltype(x)>}

namespace MyProject{

template<typename T>
class Arr1 : public std::array<T, 3>{
public:
    Arr1(T t1, T t2, T t3) : std::array<T, 3>{{t1, t2, t3}}{}
};

template<typename T>
class Arr2 : public std::array<T, 3>{    
    Arr2(T t1, T t2, T t3) : std::array<T, 3>{{t1, t2, t3}}{}
};

namespace{
  template <typename T> constexpr T defaultValue = std::numeric_limits<T>::max();

  template <typename T> const Arr1<T> defaultValue<Arr1<T>> = Arr1<T>{defaultValue<T>, defaultValue<T>, defaultValue<T>};

  template <typename T> const Arr2<T> defaultValue<Arr2<T>> = Arr2<T>{defaultValue<T>, defaultValue<T>, defaultValue<T>};
}

class S {
public:
    S() : DEFAULT_INIT(_arr1), DEFAULT_INIT(_arr2), DEFAULT_INIT(_n) {}
    S(unsigned int n) : DEFAULT_INIT(_arr1), DEFAULT_INIT(_arr2), _n{n} {}

private:
    Arr1<unsigned int> _arr1;
    Arr1<float> _arr2;
    unsigned int _n;
};
}

int main(){
    return 0;
}

导致汇编错误

/tmp/ccWrYvQ2.s: Assembler messages:
/tmp/ccWrYvQ2.s:110: Error: symbol `_ZN9MyProject12_GLOBAL__N_1L12defaultValueE' is already defined

在玩编译器浏览器时(考虑到其他使用更新版本的gcc编译的CI构建没有问题), 我发现这只会发生在gcc <6.2中,并且是由于编译器为defaultValue<Arr1<T>>defaultValue<Arr2<T>>生成相同的符号。

由于在此项目中,我们仍然希望支持(目前)有错误的操作系统和默认编译器,是否有任何解决方法使其正常工作?

2个回答

4
我们可以将提问者“过于臃肿的示例” 简化 为:
template <typename T> struct A {};

template <typename T> const T defaultValue = 0;
template <typename T> const A<T> defaultValue<A<T>> = A<T>{};

struct S {
    S() : a1{defaultValue<decltype(a1)>},
          a2{defaultValue<decltype(a2)>} {}
    A<char> a1;
    A<int> a2;
};

int main() {
    return 0;
}

在大多数 GCC 版本(GCC 6.2 之前的版本)中,你将会遇到相同的符号冲突错误(例如,在 GCC 5.3 中可以成功编译,但在 GCC 5.4 中出现了回归问题),这是 GCC 的一个 bug。

该 bug 的根本原因是 Bug 69515 - partial specialization of variable templates is broken。这两个问题在 GCC 6.2 中已得到解决。

由于在这个项目中我们仍然希望支持具有缺陷的操作系统和默认编译器(目前如此),是否有任何解决方法可以正常工作?

如果您想继续使用旧版 GCC 编译器,则需要避免使用变量模板的部分特化,而是使用元函数来包装该值,以便正确地工作。

template <typename T> struct A {};

template <typename T>
struct default_value {
    static const T value = 0;  
};

template <typename T>
struct default_value<A<T>> {
    static const A<T> value;  
};

template <typename T> 
const A<T> default_value<A<T>>::value = A<T>{};

template <typename T>
const T default_value_v = default_value<T>::value;

struct S {
    S() : a1{default_value_v<decltype(a1)>},
          a2{default_value_v<decltype(a2)>} {}
    A<char> a1;
    A<int> a2;
};

int main() {
    return 0;
}

谢谢,看起来这是一个可行的选择。抱歉示例有点冗长,我想尽可能忠实于项目代码。 - Valerio Formato
@ValerioFormato 很高兴能够帮忙。别担心,但是以后请始终优先考虑提供一个最小化的, 完整的和可验证的示例 (mcve),而不是一个特定领域的示例。SO通常对任何特定项目都不感兴趣,而强烈建议将问题保持忠实于其核心问题,而不是针对OP出现核心问题的项目特定上下文。这样做的理由是问题会更容易理解和回答,并且Q&A将有助于未来的读者,而不仅仅是OP。 - dfrib

1

也许你可以通过默认构造函数来实现默认值,请参见此处

另一种选择是仅使用类模板特化,请参见此处


是的,我们可以这样做,但我们决定不这样做。在这个项目中,用户还应该创建新的类和数据结构。我们希望提供一种“统一”的方式来初始化他们类的成员变量(我们是物理学家,而不是受过训练的开发人员,我们倾向于通过示例来教学。S 应该是一个用户在创建自己的类时可以查看的示例类)。此外,我们选择了非物理值来表示字段“无效”,但这可能不是默认构造函数的最佳值(对于 Point 类,我们可能希望默认为 (0, 0, 0))。 - Valerio Formato

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接