Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > C/C++: Общие вопросы > Не могу использовать имя "exp" в коде.


Автор: riden 29.05.24, 19:05
Visual Studio 2022, C++20.
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    #include <iostream>
    float exp = 1.0; //Error C2365 'exp': redefinition; previous definition was 'function'
     
    int main(){
        std::cout<<exp<<std::endl;
        return 0;
    }


Поражает, что я не подключаю cmath и т.п., не подключаю пространство имён std. И всё равно возникает такая херня!

Я хочу использовать имя "exp" не отказываясь от подключения заголовочного файла <iostream> и не помещая своё exp в какое-либо пространство имён.

Автор: macomics 30.05.24, 02:23
А exp это разве не встроенная функция. Она должна быть доступна вообще без подключения модулей. Наличие #include <iostream> тут не причем.

Автор: riden 30.05.24, 13:22
Цитата macomics @
А exp это разве не встроенная функция. Она должна быть доступна вообще без подключения модулей. Наличие #include <iostream> тут не причем

Нет. Это библиотечная функция (стандартная библиотека) из модуля (заголовочного файла) про математику. И для меня странно её попадание в код посредством заголовочного файла для ввода-вывода, и еще страннее её доступность без пространства имён std. Зачем такое делать???

Автор: Qraizer 30.05.24, 14:45
В библиотеке потоков C++98 был дефект, заключающийся в том, что подключив <iostream>, мы лишь получаем доступ к стандартным потокам, и по сути можем лишь... получить ссылку, например. Элементарный operator<< по-хорошему был недоступен, т.к. он определён в другом заголовке. Т.ч. в дополнение к <iostream> приходилось подключать ещё всякие там <istream> и иже с ним. В C++11 этот дефект был исправлен, т.к. Комитет решил, что <iostream> без этих дополнительных заголовков практически бесполезен, т.ч. теперь стандартизировано, что он включает <istream>, <ostream>, <ios> и <streambuf>. VS именно так и поступает (пусть и несколько окольным путём, как я не поленился посмотреть), но она идёт также немного дальше.
<streambuf> определяет std::basic_streambuf<>, а у него регламентируется поддержка локалей через std::basic_streambuf<>::imbue(). Также Стандарт регламентирует, что не только все методы фасетов константны, но и std::use_facet<> возвращает константные ссылки на фасеты. Стандарт это делает, чтобы гарантировать неизменность национальных особенностей в std::locale после создания его экземпляра, что в общем-то более чем резонно. У потоков в итоге есть прекрасное средство оптимизации: ещё внутри std::basic_streambuf<>::imbue() они могут выполнить все необходимые подготовительные операции для подстройки под новые пользовательские соглашения и предпочтения. Поэтому стандартная библиотека в VS в <streambuf> тянет кучу других заголовков, и т.к. все (почти) фасеты являются шаблонами, ибо зависят от типа символа, которым конкретизируются, char там, или wchar_t, или ещё каким, то и их методы лежат в заголовках (как того требует правила работы с инстанцированием шаблонов вообще и ODR в частности). В итоге в эту кучу входит <cmath>, который нужен std::num_put<> (а может и std::num_get<>). Так <cmath> оказывается косвенно включённым в единицу трансляции, которая подключает <iostream>.
Это был первый вопрос "как так-то?". Вопрос второй "а имела ли право VS так поступать". Я пересмотрел Стандарты C++ и C и не обнаружил там ни одного запрета на то, чтобы какой-нибудь стандартный заголовок включал другой заголовок, также из числа стандартных. Так что ответ "да, имела". Вопрос третий "а насколько это хорошо" уже далеко не так очевиден.
Спервоначалу следует заметить, что Стандарты в один голос утверждают, что использование библиотечных сущностей без предварительного подключения соответствующего заголовка ведёт к неопределённому поведению. Формально символ exp относится к библиотеке C, а не С++, однако библиотека C является частью С++, так что по-любому присутствует в языке и наследует все особенности использования. (При этом символы из глобальной области видимости дублируются в std.) Твой код использует символ exp, но не подключает заголовок <cmath>, в итоге ты имеешь то, что видишь: одно из возможных проявлений неопределённого поведения. Твоё желание использовать именно exp понятно, я сам когда-то ловил грабли с exit, но в итоге плюнул и переименовал её в quit. Не могу не согласиться ни с компилятором, ни со Стандартом, ибо просто взглянув на твой код, я тоже словил "так, стоп" и только через пару секунд сообразил, что ты хотел написать, куда уж тут бездушным кремниевым болванкам. Не ну и правда, что такое exp я знаю, отсутствие подключения <cmath> ещё заметить надо, и заметив, непонятно, где баг: программер то ли набажил с функцией, то ли с заголовком, то ли с присваиванием, то ли вообще не набажил. Я бы даже сказал, что VS правильно поступила, что выругалась ещё при компиляции, предупредив неопределённое поведение. Если б он косвенно не подключил <cmath> при компиляции, могло получиться так, чтоб на множественное определение exp мог выругаться линкер, т.к. предкомпилированная exp() уже лежит в где-нибудь в glibc++.a и тянется вместе с каким-нибуль operator<<. А мог и не выругаться, т.к. это банальное нарушение ODR, для которого не требуется диагностики, в итоге полгода нормально работавшая программа вдруг начинает падать на ровном месте, когда exp понадобилось присвоить новое значение.
Мораль в целом вот: не стоит использовать Стандартные идентификаторы для своих целей.

Добавлено
P.S. И да – все символы стандартной библиотеки C находятся в глобальном пространстве имён и в C++ тоже. Причём для этого есть и более веская причина, нежели легаси-код или там удобство. Причина в ADL, без которой нынешний C++ просто не существовал бы, и который пришлось внести в язык ещё в середине 90-ых, т.е. до первого стандарта C++98.

Автор: riden 30.05.24, 16:04
Цитата Qraizer @
В библиотеке потоков C++98 был дефект, заключающийся в том, что подключив <iostream>, мы лишь получаем доступ к стандартным потокам, и по сути можем лишь... получить ссылку, например. Элементарный operator<< по-хорошему был недоступен, т.к. он определён в другом заголовке. Т.ч. в дополнение к <iostream> приходилось подключать ещё всякие там <istream> и иже с ним. В C++11 этот дефект был исправлен, т.к. Комитет решил, что <iostream> без этих дополнительных заголовков практически бесполезен, т.ч. теперь стандартизировано, что он включает <istream>, <ostream>, <ios> и <streambuf>. VS именно так и поступает (пусть и несколько окольным путём, как я не поленился посмотреть), но она идёт также немного дальше.
<streambuf> определяет std::basic_streambuf<>, а у него регламентируется поддержка локалей через std::basic_streambuf<>::imbue(). Также Стандарт регламентирует, что не только все методы фасетов константны, но и std::use_facet<> возвращает константные ссылки на фасеты. Стандарт это делает, чтобы гарантировать неизменность национальных особенностей в std::locale после создания его экземпляра, что в общем-то более чем резонно. У потоков в итоге есть прекрасное средство оптимизации: ещё внутри std::basic_streambuf<>::imbue() они могут выполнить все необходимые подготовительные операции для подстройки под новые пользовательские соглашения и предпочтения. Поэтому стандартная библиотека в VS в <streambuf> тянет кучу других заголовков, и т.к. все (почти) фасеты являются шаблонами, ибо зависят от типа символа, которым конкретизируются, char там, или wchar_t, или ещё каким, то и их методы лежат в заголовках (как того требует правила работы с инстанцированием шаблонов вообще и ODR в частности). В итоге в эту кучу входит <cmath>, который нужен std::num_put<> (а может и std::num_get<>). Так <cmath> оказывается косвенно включённым в единицу трансляции, которая подключает <iostream>.
Это был первый вопрос "как так-то?". Вопрос второй "а имела ли право VS так поступать". Я пересмотрел Стандарты C++ и C и не обнаружил там ни одного запрета на то, чтобы какой-нибудь стандартный заголовок включал другой заголовок, также из числа стандартных. Так что ответ "да, имела". Вопрос третий "а насколько это хорошо" уже далеко не так очевиден.
Спервоначалу следует заметить, что Стандарты в один голос утверждают, что использование библиотечных сущностей без предварительного подключения соответствующего заголовка ведёт к неопределённому поведению. Формально символ exp относится к библиотеке C, а не С++, однако библиотека C является частью С++, так что по-любому присутствует в языке и наследует все особенности использования. (При этом символы из глобальной области видимости дублируются в std.) Твой код использует символ exp, но не подключает заголовок <cmath>, в итоге ты имеешь то, что видишь: одно из возможных проявлений неопределённого поведения. Твоё желание использовать именно exp понятно, я сам когда-то ловил грабли с exit, но в итоге плюнул и переименовал её в quit. Не могу не согласиться ни с компилятором, ни со Стандартом, ибо просто взглянув на твой код, я тоже словил "так, стоп" и только через пару секунд сообразил, что ты хотел написать, куда уж тут бездушным кремниевым болванкам. Не ну и правда, что такое exp я знаю, отсутствие подключения <cmath> ещё заметить надо, и заметив, непонятно, где баг: программер то ли набажил с функцией, то ли с заголовком, то ли с присваиванием, то ли вообще не набажил. Я бы даже сказал, что VS правильно поступила, что выругалась ещё при компиляции, предупредив неопределённое поведение. Если б он косвенно не подключил <cmath> при компиляции, могло получиться так, чтоб на множественное определение exp мог выругаться линкер, т.к. предкомпилированная exp() уже лежит в где-нибудь в glibc++.a и тянется вместе с каким-нибуль operator<<. А мог и не выругаться, т.к. это банальное нарушение ODR, для которого не требуется диагностики, в итоге полгода нормально работавшая программа вдруг начинает падать на ровном месте, когда exp понадобилось присвоить новое значение.
Мораль в целом вот: не стоит использовать Стандартные идентификаторы для своих целей.

Добавлено Сегодня, 17:58
P.S. И да – все символы стандартной библиотеки C находятся в глобальном пространстве имён и в C++ тоже. Причём для этого есть и более веская причина, нежели легаси-код или там удобство. Причина в ADL, без которой нынешний C++ просто не существовал бы, и который пришлось внести в язык ещё в середине 90-ых, т.е. до первого стандарта C++98.


Спасибо за подробный ответ.

Автор: macomics 30.05.24, 16:15
Тогда тут получается ругается линкер?
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    int exp = 1;
     
    int main(){
        return exp;
    }

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    $ g++ -o c1.o -c 1.cpp
    1.cpp:1:5: предупреждение: built-in function «exp» declared as non-function [-Wbuiltin-declaration-mismatch]
        1 | int exp = 1;
          |     ^~~
Хотя я просто создаю объектный файл без линкера.

ADD: Хотя, подозреваю, что это gcc так перестраховывается, чтобы предупредить появление подобных ошибок.

Автор: Qraizer 30.05.24, 16:41
Нет, конечно. Компилятор. Когда Стандарт на нарушение говорит, мол, диагностики не требуется, это не означает, что её не должно быть, это означает лишь, что она может отсутствовать.
Я проверил на g++ под убундой, там всё нормально и компилится, и собирается. Типичный пример неопределённого поведения, в разных реализациях может быть по-разному. В частности и ожидаемым образом, как у меня. Но.
Чуть изменим ситуацию:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    // f1.cpp
    #include <iostream>
     
    float exp = 1.0;
     
    float foo(float);
     
    int main()
    {
      std::cout << exp << '\t' << foo(exp) << std::endl;
     
      return 0;
    }
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    // f2.cpp
    #include <cmath>
     
    float foo(float x)
    {
      return exp(x);
    }

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    censored:~/Документы/c++$ g++ f1.cpp f2.cpp
    censored:~/Документы/c++$ ./a.out
    Ошибка сегментирования (стек памяти сброшен на диск)
    censored:~/Документы/c++$
Нарушение ODR собственной персоной, для которой диагностики не требуется. Но:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    // f2.cpp
    #include <cmath>
     
    float foo(float x)
    {
      return std::exp(x);
    }

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    censored:~/Документы/c++$ g++ f1.cpp f2.cpp
    censored:~/Документы/c++$ ./a.out
    1       2.71828
    censored:~/Документы/c++$
:whistle: Потому что в C не было пространств имён (точнее, они были, но абсолютно в другом смысле), поэтому если в C++ для exp() в глобальном скопе требовалось связывание extern "C" для совместимости с библиотекой C, то внутри namespace std это уже не требуется и даже невозможно, поэтому там используется extern "C++", что делает символы там и в глобальном скопе для линкера разными.

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)