Поведение std::function зависит от компилятора

89
18 декабря 2020, 04:40

Есть код

#include <functional>
#include <iostream>
template<typename T>
struct A{
    static inline std::function<void()> func;
    static inline int value;
    static bool set(std::function<void()> func_, int value_){
        func = func_;
        value = value_;
        return static_cast<bool>(A<int>::func);
    }
    static void call(){
        std::cout << value << " : " << std::boolalpha << static_cast<bool>(A<int>::func) << std::endl;
        func();
    }
};
struct B{
    static void func(){
    }
    static inline bool res = A<int>::set(B::func, 42);// || static_cast<bool>(A<int>::func);
};
int main(){
    A<int>::call();
    return 0;
}

для gcc (начиная с 7 и до последнего) в результате получается вывод

42 : false
terminate called after throwing an instance of 'std::bad_function_call'
  what():  bad_function_call

т.е. объект в переменной func пустой

если раскомментировать вторую часть строки

static inline bool res = A<int>::set(B::func, 42) || static_cast<bool>(A<int>::func);

то вывод становится тем, который нужен (при этом раскоментированная часть даже не вызывается)

42 : true
0

при этом в clang любой версии все нормально и с закоментированным куском.

Что не так с кодом или с gcc?

Спасибо HolyBlackCat

Перемещение static func переменной в функцию отлично помогло. Код ниже работает как ожидалось

#include <functional>
#include <iostream>
template<typename T>
struct A{
    static std::function<void()>& func(){
        static std::function<void()> func;
        return func;
    }
    // static inline std::function<void()> func;
    static bool set(std::function<void()> func_){
        func() = func_;
        return static_cast<bool>(func());
    }
    static void call(){
        std::cout << std::boolalpha << static_cast<bool>(A<int>::func()) << std::endl;
        func()();
    }
};
struct B{
    static void func(){
        std::cout << "func" << std::endl;
    }
    static inline bool res = A<int>::set(B::func);// || static_cast<bool>(A<int>::func);
};
int main(){
    A<int>::call();
    return 0;
}
Answer 1

Что не так с кодом или с gcc?

В комментариях правильно пишут, дело в порядке инициализации.

В обоих случаях правильная работа не гарантируется.

Судя по cppreference, инициализация статических переменных в шаблонах классов (когда переменные не были явно специализированы) выполняется в произвольном порядке, в том числе относительно всей остальной (неконстантной) инциализации глобальных/статических переменных.

Так что компилятор вполне может инициализировать B::res до A<int>::func, что GCC и делает в первом случае.

То, что GCC меняет порядок инициализации, когда понимает, что B::res зависит от A<int>::func (второй случай) - не более, чем любезность разработчиков GCC.

Если B::res инициализируется до A<int>::func, то это приводит к использованию func до его инициализации и неопределенному поведению.

Чтобы не отстреливать себе постоянно ногу порядком инициализации, можно помещать чувствительные к порядку инициализации переменные в функции:

В class A:

static std::function<void()> &func()
{
    static std::function<void()> ret;
    return ret;
}
READ ALSO
Примеры указателей в с++ [закрыт]

Примеры указателей в с++ [закрыт]

Хотите улучшить этот вопрос? Переформулируйте вопрос так, чтобы он был сосредоточен только на одной проблеме

92
Qt5: как обновить данные в QTableWidget

Qt5: как обновить данные в QTableWidget

Есть некоторая таблица в которую из базы данных через промежуточную структуру подгружаются данные(вектор объектов класса)В структуре данные...

106
Звёздочка (*) перед классом

Звёздочка (*) перед классом

Что означает такая конструкция?

93
Отловить сочетание клавиш в консоли

Отловить сочетание клавиш в консоли

1) Код должен быть или кроссплатформенный, или 2а варианта кода (под Windows и Linux)

132