【问题标题】:Unexpected result for a type counter using templates with function local types in Clang在 Clang 中使用具有函数本地类型的模板的类型计数器的意外结果
【发布时间】:2015-09-21 21:43:16
【问题描述】:

我编写了一个基于两种类型的类模板,根据其模板参数为其分配了唯一索引:

template<typename SK,typename T>
struct Component {
    static uint const index;
};

期望对于每个新类型,index 都会递增:

Component<X,A>::index; // 0
Component<X,B>::index; // 1

Component<Y,A>::index; // 0
Component<Y,B>::index; // 1
// ...etc

分配索引的完整代码如下:

using uint = unsigned int;

template<typename SK,typename T>
struct Component
{
    static uint const index;
};

template<typename SK>
class ComponentCount
{
    template<typename CSK,typename CT>
    friend struct Component;

private:
    template<typename T>
    static uint next() {
        return ComponentCount<SK>::get_counter();
    }

    static uint get_counter()
    {
        static uint counter = 0;
        return counter++;
    }
};

这在 GCC (5.1) 和 MSVC 中按预期工作,并进行了以下测试:

// global scope
struct X {};
struct Y {};

int main()
{
    // function scope
    struct Z{};

    uint x0 = Component<X,int>::index;
    uint x1 = Component<X,double>::index;
    uint x2 = Component<X,double>::index;
    uint x3 = Component<X,std::string>::index;
    uint x4 = Component<X,int>::index;
    uint x5 = Component<X,int>::index;

    std::cout << x0 << ", " << x1 << ", " << x2 << ", "
              << x3 << ", " << x4 << ", " << x5 << std::endl;

    uint y0 = Component<Y,int>::index;
    uint y1 = Component<Y,double>::index;
    uint y2 = Component<Y,double>::index;
    uint y3 = Component<Y,std::string>::index;
    uint y4 = Component<Y,int>::index;
    uint y5 = Component<Y,int>::index;

    std::cout << y0 << ", " << y1 << ", " << y2 << ", "
              << y3 << ", " << y4 << ", " << y5 << std::endl;

    uint z0 = Component<Z,int>::index;
    uint z1 = Component<Z,double>::index;
    uint z2 = Component<Z,double>::index;
    uint z3 = Component<Z,std::string>::index;
    uint z4 = Component<Z,int>::index;
    uint z5 = Component<Z,int>::index;

    std::cout << z0 << ", " << z1 << ", " << z2 << ", "
              << z3 << ", " << z4 << ", " << z5 << std::endl;

    return 0;
}

输出是

0, 1, 1, 2, 0, 0
0, 1, 1, 2, 0, 0
0, 1, 1, 2, 0, 0

但是对于 Clang (3.6.1),输出不同:

0, 1, 1, 2, 0, 0
0, 1, 1, 2, 0, 0
5, 2, 2, 3, 5, 5

具体来说,为函数局部类型(即'Z')生成的索引会做一些奇怪的事情。就像每次调用 Component&lt;Z,...&gt; 时它们都会增加并重新分配索引。

为什么会这样?它是编译器错误吗?将函数本地类型与模板一起使用时(C++11 后)是否有任何特殊注意事项?

一个完整的例子可以在这里找到: http://coliru.stacked-crooked.com/a/7fcb989ae6eab476

== 编辑 ==

我决定将问题发布到 clang 的 bugtracker,所以如果其他人遇到这个问题:

https://llvm.org/bugs/show_bug.cgi?id=24048

【问题讨论】:

  • 看起来 clang 期望 Component&lt;SK, T&gt;::index 的初始化表达式是纯的。删除index 上的const 说明符会得到expected result
  • 我不完全确定这个程序是否格式正确;也许它格式正确,但输出未指定。例如,静态数据成员的不同实例化应该具有无序初始化。然而,初始化的顺序决定了程序的输出。
  • 函数内的实例化顺序是否定义明确也不清楚。但是,我看不出有任何理由让53 出现在输出中(我相信这需要UB,而且我在这里看不到任何UB,只有未指定的顺序)。另见groups.google.com/a/isocpp.org/d/msg/std-discussion/M6aJMH_ewoM/…

标签: c++ templates c++11 clang


【解决方案1】:

这对我来说似乎是一个错误。我不知道应该对函数局部类型和全局类型产生影响的 C++11 规则。

如果您dump the assembler,您会注意到,虽然XY 的值是实际计算的,但对于Z,它们是预先计算的。根本不生成计数器静态变量初始化的保护变量。

.Ltmp87:
    #DEBUG_VALUE: main:z5 <- 5
    #DEBUG_VALUE: main:z4 <- 5
    #DEBUG_VALUE: main:z3 <- 3
    #DEBUG_VALUE: main:z2 <- 2
    #DEBUG_VALUE: main:z1 <- 2
    #DEBUG_VALUE: main:z0 <- 5
    .loc    6 54 5                  # main.cpp:54:5
    movl    std::cout, %edi
    movl    $5, %esi
    .loc    6 74 5                  # main.cpp:74:5
    callq   std::basic_ostream<char, std::char_traits<char> >::operator<<(unsigned int)

【讨论】:

    【解决方案2】:

    我不知道这是否是 clang 中的错误,但您当前版本的 Component::template next 似乎有问题。

    为了说明这一点,我将函数成员的访问权限从“私有”更改为“公共”。然后使用以下代码:

    for(int i=0; i<5;++i)
        std::cout<< ComponentCount<int>::next<int>() <<" ";
    

    我明白了:

    0 1 2 3 4

    因此,也许您应该考虑将 next 的实现更改为如下所示:

    template<typename SK>
    class ComponentCount
    {
        template<typename CSK,typename CT>
        friend struct Component;
    
    private:
        template<typename T>
        static uint next() {
            static uint index = get_counter();
            return index;
        }
    
        static uint get_counter()
        {
            static uint counter = 0;
            return counter++;
        }
    };
    

    有了这个,我在 gcc、clang3.6 和 VS2015 上得到了预期的结果

    g++ (GCC) 5.1.0 版权所有 (C) 2015 Free Software Foundation, Inc. 是免费软件;查看复制条件的来源。没有 保修单;甚至不适用于适销性或特定的适用性 目的。

    0, 1, 1, 2, 0, 0

    0, 1, 1, 2, 0, 0

    0, 1, 1, 2, 0, 0

    clang 版本 3.6.0 (tags/RELEASE_360/final 235480) 目标: x86_64-unknown-linux-gnu 线程模型:posix

    0, 1, 1, 2, 0, 0

    0, 1, 1, 2, 0, 0

    0, 1, 1, 2, 0, 0

    final code + runs of gcc & clang on coliru

    [编辑]错别字

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-17
      • 1970-01-01
      • 2020-03-28
      • 2018-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-11
      相关资源
      最近更新 更多