【问题标题】:C++ template variable specialization with GCC 5.4GCC 5.4 的 C++ 模板变量特化
【发布时间】:2020-07-21 12:34:45
【问题描述】:

我在我正在从事的项目的 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

使用编译器资源管理器(并且考虑到其他 CI 构建使用更新版本的 gcc 编译没有问题)我已经看到这只发生在 gcc defaultValue<Arr1<T>> 和 defaultValue&lt;Arr2&lt;T&gt;&gt;。

由于在这个项目中,我们仍然希望(目前)使用其默认编译器支持有故障的操作系统,是否有任何解决方法可以让它正常工作?

【问题讨论】:

    标签: c++ templates gcc


    【解决方案1】:

    我们可以将 OP 的 overly bloated example 最小化为:

    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 6.2 之前的大多数 GCC 版本上都会出现相同的符号冲突错误(它可以在 GCC 5.3 上成功编译,但在 GCC 5.4 中已经回归)。

    这是 GCC 错误

    根本原因是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;
    }
    

    【讨论】:

    • 谢谢,这看起来是一个可行的选择。抱歉这个臃肿的例子,我想尽可能忠实于项目代码。
    • @ValerioFormato 乐于助人。不用担心,但为了将来参考,始终支持minimal, complete and verifiable example (mcve) 而不是特定领域的。 SO 通常对任何特定项目不感兴趣,而是强烈建议让问题忠实于其核心问题,而不是针对 OP 出现核心问题的项目特定上下文。理由是这个问题将更容易掌握和回答,并且问答将对未来的读者有用,而不仅仅是对 OP。
    【解决方案2】:

    也许您可以通过默认构造函数实现默认值,请参阅here。

    另一种选择是仅使用类模板特化,请参阅here。

    【讨论】:

    • 是的,我们可以这样做,但我们决定不这样做。在这个项目中,用户还应该创建新的类和数据结构。我们希望提供一种“统一”的方式来初始化其类的成员变量(我们是物理学家,而不是训练有素的开发人员,我们倾向于“通过示例”进行教学。S 应该是用户可以查看的示例类创建他自己的时候)。此外,我们在这里选择了非物理值来表示字段“无效”,但这可能不是默认构造函数的最佳值(对于Point 类,我们可能希望默认为(0, 0, 0))
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多