Инициализация статической переменной C++ внутри функции шаблона

я заметил довольно странное поведение инициализации статической переменной в шаблонах функций. Рассмотрим следующий пример:

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");
}

пока f на test статично, я ожидал createFile вызывается только один раз. Однако называется она дважды.

проведя некоторое время, играя с проблемой, я заметил, что удаление ссылки const из аргумента в test фиксирует его. Еще одна интересная вещь заключается в том, что длина строки прошли на функцию также влияет инициализация: когда длина параметров равна, статическая переменная инициализируется только один раз, в противном случае происходит новая инициализация.

может кто-нибудь объяснить это? Решения / обходные пути, помимо упомянутых, очень приветствуются.

2 ответов


буквальное "один" - это const char [4].

этот код:

test("one")

в идеале хотелось бы назвать test(const char (&)[4])

это работает для test(const T&) (поскольку const char (&) [4] можно привязать к const char (const&) [4]).

но это не работает для test(T t) потому что вы не можете передавать строковые литералы по значению. Они передаются по ссылке.

, const char[4] может распадаться на const char*, который может соответствовать template<class T> void func(T t).

доказательство в пудинг:

#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");
}

пример результатов (clang):

test_const for literal one T is a c and N is 4
test_const for literal three T is a c 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

вот версия с demangled именами (будет компилироваться на clang и 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");
}

ожидаемый результат:

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*

в качестве дополнения к ответу @RichardHodges, который объясняет, почему используются разные экземпляры, легко заставить только один, потому что массивы могут распадаться на указатель с явным экземпляром шаблона:

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

результат в одном вызове createFile.

на самом деле (как сказано в комментарии BoBTFish), это именно то, что происходит, когда вы пишете:

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

независимо от размера массива, массив автоматически распадается на const char * потому что C++ не позволяет назначать непосредственно массивы.

кстати, void main() is плохо. Всегда используйте int main() и явное возвращение.