Неожиданный результат для счетчика типов с использованием шаблонов с функциями локальных типов в Clang

Я написал шаблон класса, основанный на двух типах, которому присваивается уникальный индекс, основанный на его параметрах шаблона:

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<Z,...>.

Почему это происходит? Это ошибка компилятора? Существуют ли какие-либо особые соображения при использовании функциональных локальных типов с шаблонами (post С++ 11)?

Полный пример можно найти здесь: http://coliru.stacked-crooked.com/a/7fcb989ae6eab476

== Правка ==

Я решил опубликовать эту проблему для clang bugtracker, поэтому, если кто-то еще сталкивается с этим:

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

Ответ 1

Это выглядит как ошибка для меня. Я не знаю правил С++ 11, которые должны иметь значение для локальных типов функций и глобальных типов.

Если вы выгрузите ассемблер, вы можете заметить, что в то время как для X и Y значения фактически вычисляются, для 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.

Чтобы показать это, я изменил доступ к члену функции от "private" до "public". Затем со следующим кодом:

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

Я получаю:

0 1 2 3 4

Так что, возможно, вам стоит подумать о том, чтобы изменить вашу реализацию следующего на что-то вроде этого:

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 Copyright (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 version 3.6.0 (теги/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

окончательный код + пробеги gcc и clang на coliru

[править] опечатки