Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected result for a type counter using templates with function local types in Clang

I wrote a class template based on two types that is assigned a unique index based on its template parameters:

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

The expectation is that for each new type, index is incremented:

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

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

The complete code that assigns the indices is as follows:

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++;
    }
};

This works as expected in GCC (5.1) and MSVC with the following test:

// 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;
}

The output is

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

However with Clang (3.6.1), the output differs:

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

Specifically, the indices generated for function local types (ie. 'Z') do something strange. Its like they increment and reassign the index every time Component<Z,...> is called.

Why does this happen? Is it a compiler bug? Are there any special considerations when using function local types with templates (post C++11)?

A complete example can be found here: http://coliru.stacked-crooked.com/a/7fcb989ae6eab476

== Edit ==

I decided to post the issue to clang's bugtracker, so if anyone else runs into this:

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

like image 236
Prismatic Avatar asked Jul 04 '15 05:07

Prismatic


2 Answers

This looks like a bug to me. I am not aware of C++11 rules that should make a difference for function local types and global types.

If you dump the assembler, you can notice that while for X and Y the values get actually computed, for Z they are precomputed. The guard variables for the counter static variable initialization are not generated at all.

.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)
like image 100
wilx Avatar answered Oct 15 '22 12:10

wilx


I don't know if this is a bug in clang, but it seems that you have an issue with your current version of Component::template next.

To show this, I changed the access to the function member from "private" to "public". Then with the following code:

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

I get:

0 1 2 3 4

So maybe you should consider changing your implemention of next into something like this:

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++;
    }
};

With this I get the expected result on gcc, clang3.6 and VS2015

g++ (GCC) 5.1.0 Copyright (C) 2015 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

0, 1, 1, 2, 0, 0

0, 1, 1, 2, 0, 0

0, 1, 1, 2, 0, 0

clang version 3.6.0 (tags/RELEASE_360/final 235480) Target: x86_64-unknown-linux-gnu Thread model: 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

[edit] typos

like image 25
Marouane Fazouane Avatar answered Oct 15 '22 13:10

Marouane Fazouane