Exporting a template method creates multiple static variables in windows

Viewed 69

I have a class in a library, if that class has a template method which has a static variable, using the method from within the library will access a different static variable than using the method from outside the library. This behavior happen in windows, in linux it works as expected. I prepared an example with two scenarios, in both scenarios classes are defined in a library (LibA) and the test program is linked to it (temptest). Code is simplified in this explanation, full code is available in this github repository https://github.com/pyaggi/temptest. Example code uses cmake, so it will be easy to compile in any platform.

1- Scenario with a class template

//liba.h (library)
template<typename T>
class TestClass_template
{
public:
    TestClass_template()
    {

    }
    template<typename T1>
    static T1 incCounter()
    {
        static T1 counter=0;
        return counter++;
    }
private:
    T val;
};
class LIBA_PUBLIC TestClass_long:public TestClass_template<long>
{
public:
    TestClass_long();
    static short test_incCounter_short();
};



//liba.cpp (library)
TestClass_long::TestClass_long()
{

}
short TestClass_long::test_incCounter_short()
{
    return incCounter<short>();
}



//main.cpp (temptest, program)
std::cout<<"Output from class template example"<<std::endl;
for  (int c=0;c<2;c++)
{
    std::cout<<"TestClass<long>::incCounter_short "<<TestClass_template<long>::incCounter<short>()<<std::endl;
    TestClass_long::test_incCounter_short();
    std::cout<<"TestClass_long::incCounter_short "<<TestClass_long::incCounter<short>()<<std::endl;
    TestClass_long::test_incCounter_short();
}

Output in Linux

Output from templated class example
TestClass<long>::incCounter_short 0
TestClass_long::incCounter_short 2
TestClass<long>::incCounter_short 4
TestClass_long::incCounter_short 6

Ouput in Windows

Output from templated class example
TestClass<long>::incCounter_short 0
TestClass_long::incCounter_short 1
TestClass<long>::incCounter_short 2
TestClass_long::incCounter_short 3

In windows calling TestClass_long::test_incCounter_short(); access a different static variable producing a sequencial output (0,1,2,3) in linux it access the same static hence producing an interleaved output (0,2,4,6).

2- Scenario with a regular class

//liba.h (library)
class LIBA_PUBLIC TestClass
{
public:
    TestClass()
    {

    }
    template<typename T1>
    static T1 incCounter()
    {
        static T1 counter=0;
        return counter++;
    }
    static short test_incCounter_short();
private:
    long val;
};



//liba.cpp (library)
short TestClass::test_incCounter_short()
{
    return incCounter<short>();
}



//main.cpp (temptest, program)
std::cout<<"Output from regular class example"<<std::endl;
for  (int c=0;c<2;c++)
{
    std::cout<<"TestClass::incCounter_short "<<TestClass::incCounter<short>()<<std::endl;
    TestClass::test_incCounter_short();
    std::cout<<"TestClass::incCounter_short "<<TestClass::incCounter<short>()<<std::endl;
    TestClass::test_incCounter_short();
}

Output in Linux

Output from templated class example
TestClass::incCounter_short 0
TestClass::incCounter_short 2
TestClass::incCounter_short 4
TestClass::incCounter_short 6

Ouput in Windows

Output from templated class example
TestClass::incCounter_short 0
TestClass::incCounter_short 1
TestClass::incCounter_short 2
TestClass::incCounter_short 3

Linux test was performed with g++ and clang.

Windows test was performed with msvc(cl.exe) and clang (msvc-cl.exe).

So I have two questions:

1- What is the expected behavior according to C++17 ?

2- Is it possible to make MSVC behave like linux ?

1 Answers

After doing some research, reading this excellent article from Michele Caini, a great generic programmer; reading this rejected bug report from Microsoft. I can answer the two questions:

1- What is the expected behavior according to C++17 ?

None of the current revisions of the standard address the handling of libraries, crossing boundary codes are not standardized so it is up to the compiler designer.

2- Is it possible to make MSVC behave like linux ? Well, the short answer is no. But sometimes there is a way to workaround this issue.

Xiang Fan at MSFT (check the bug report) proposed a solution using macro declarations to only define the templated static variable in one unit. That kind of solution just don't work in most scenarios because you defeat the whole purpouse of generic programing which is the idea that you don't know what kind of type will you be using and where.

I found a solution that works for me if the static variable you are creating doesn't depend on the template's type. You have to use a method or a global function that retrieves the static variable for you. This is not the case stated in the answer, but sometimes you can remodel the problem to go in this direction. I'll give you an example without classes for simplicity:

//example.h
unsigned long long &bigStatic() ;
template<typename T>
T incCounter()
{
    return static_cast<T>(bigStatic()++);
}

//example.cpp
unsigned long long &bigStatic() 
{
    static unsigned long long big=0;
    return big;
}

Related