C ++ static variable initialization inside template function

I noticed a rather strange behavior of initializing a static variable in function templates. Consider the following example:

MyFile * createFile() { std::cout << "createFile" << std::endl; return nullptr; } template <typename T> void test(const T& t) //void test(T t) { static MyFile *f = createFile(); } void main() { test("one"); //test("two"); test("three"); } 

As long as f in test is static, I expected createFile be called only once. However, it is called twice.

After spending some time playing around with the problem, I noticed that removing the const link from the argument in test fixes it. Another interesting thing: the length of the string passed to the function also affects initialization: when the length of the parameters is equal, the static variable is initialized only once, otherwise a new initialization occurs.

Can anyone explain this? Solutions / workarounds other than those mentioned are welcome.

+7
c ++ static c ++ 11 templates
source share
2 answers

The literal "one" is const char [4] .

this code:

 test("one") 

Ideally, I would like to call test(const char (&)[4])

This works for test(const T&) (because const char (&) [4] can bind to const char (const&) [4] ).

But it cannot work for test(T t) because you cannot pass string literals by value. They are transmitted by reference.

However, const char[4] may fade to const char* , which may correspond to template<class T> void func(T t) .

The proof is in the pudding:

 #include <cstdint> #include <iostream> #include <typeinfo> template <typename T, std::size_t N> void test_const(const T(&t)[N]) { std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << " and N is " << N << std::endl; } template <typename T> void test_mutable(T &t) { std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << std::endl; } template <typename T> void test_const_ref(const T &t) { std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << std::endl; } template <typename T> void test_copy(T t) { std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << std::endl; } int main() { test_const("one"); test_const("three"); test_mutable("one"); test_mutable("three"); test_const_ref("one"); test_const_ref("three"); test_copy("one"); test_copy("three"); } 

Examples of results (clang):

 test_const for literal one T is ac and N is 4 test_const for literal three T is ac and N is 6 test_mutable for literal one T is a A4_c test_mutable for literal three T is a A6_c test_const_ref for literal one T is a A4_c test_const_ref for literal three T is a A6_c test_copy for literal one T is a PKc test_copy for literal three T is a PKc 

Here is the version with demangled names (will compile on clang and gcc):

 #include <cstdint> #include <iostream> #include <typeinfo> #include <cstdlib> #include <cxxabi.h> std::string demangle(const char* name) { int status = -1; // enable c++11 by passing the flag -std=c++11 to g++ std::unique_ptr<char, void(*)(void*)> res { abi::__cxa_demangle(name, NULL, NULL, &status), std::free }; return (status==0) ? res.get() : name ; } template <typename T, std::size_t N> void test_const(const T(&t)[N]) { std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << " and N is " << N << std::endl; } template <typename T> void test_mutable(T &t) { std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << std::endl; } template <typename T> void test_const_ref(const T &t) { std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << std::endl; } template <typename T> void test_copy(T t) { std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << std::endl; } int main() { test_const("one"); test_const("three"); test_mutable("one"); test_mutable("three"); test_const_ref("one"); test_const_ref("three"); test_copy("one"); test_copy("three"); } 

expected output:

 test_const for literal one T is a char and N is 4 test_const for literal three T is a char and N is 6 test_mutable for literal one T is a char [4] test_mutable for literal three T is a char [6] test_const_ref for literal one T is a char [4] test_const_ref for literal three T is a char [6] test_copy for literal one T is a char const* test_copy for literal three T is a char const* 
+6
source share

As a complement to @RichardHodges, the answer to this question explains why different instanciations are used, it is easy to force only one thing, because arrays can decay to a pointer with explicit template initialization:

 test<const char *>("one"); test<const char *>("two"); test<const char *>("three"); 

will result in a single call to createFile .

In fact (as BoBTFish said in a comment), this is exactly what happens when you write:

 template <typename T> void test(const T t) 

Regardless of the size of the array, the array automatically decomposes into const char * , because C ++ does not allow you to directly assign arrays.

By the way, void main() bad. Always use int main() and explicit return.

0
source share

All Articles