Модули С++: умножение определенных символов больше не является ошибкой?

Если у вас есть две классические единицы перевода, которые определяют один и тот же символ (скажем, auto fun0() -> void), я получаю fatal error LNK1169: one or more multiply defined symbols found в MSVC, поскольку это нарушает ODR.

Одним из первых шагов, который я сделал с модулями C++, было тестирование поведения с этим основным принципом. Итак, у нас есть два файла модуля (module0.ixx и module1.ixx) с почти идентичным содержимым:

// module0.ixx
export module module0;
import <cstdio>;
export void f_test() { printf("f_test()\n"); }

// module1.ixx
export module module1;
import <cstdio>;
export void f_test() { printf("f_test()\n"); }

В моем main.cpp я делаю

import module0;
import module1;
auto main() -> int{
    f_test();
}

К моему удивлению, это компилируется просто отлично. С этим возникают ожидаемые проблемы: если определение отличается, поведение зависит от порядка и т. д. Ожидается ли это? Это было 5 минут игры с модулями и кажется довольно запутанным.

FWIW, clang разрешает несколько определений в разных модулях только в том случае, если определения одинаковы.

n. m. 14.02.2023 10:47
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Калькулятор CGPA 12 для семестра
Калькулятор CGPA 12 для семестра
Чтобы запустить этот код и рассчитать CGPA, необходимо сохранить код как HTML-файл, а затем открыть его в веб-браузере. Для этого выполните следующие...
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
О тренинге HTML JavaScript :HTML (язык гипертекстовой разметки) и CSS (каскадные таблицы стилей) - две основные технологии для создания веб-страниц....
Как собрать/развернуть часть вашего приложения Angular
Как собрать/развернуть часть вашего приложения Angular
Вам когда-нибудь требовалось собрать/развернуть только часть вашего приложения Angular или, возможно, скрыть некоторые маршруты в определенных средах?
Запуск PHP на IIS без использования программы установки веб-платформы
Запуск PHP на IIS без использования программы установки веб-платформы
Установщик веб-платформы, предлагаемый компанией Microsoft, перестанет работать 31 декабря 2022 года. Его закрытие привело к тому, что мы не можем...
Оптимизация React Context шаг за шагом в 4 примерах
Оптимизация React Context шаг за шагом в 4 примерах
При использовании компонентов React в сочетании с Context вы можете оптимизировать рендеринг, обернув ваш компонент React в React.memo сразу после...
1
1
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

«Множественно определенные символы больше не являются ошибкой?» - Они никогда не были обязаны быть ошибкой. Нарушение ODR является «неправильным; диагностика не требуется». Что в основном означает, что возникает неопределенное поведение, как вы видите здесь.

Эта проблема не характерна для модулей. «Старый C++» может демонстрировать такое же поведение.

// oops_header.h
#ifndef OOPS
#error OOPS
#endif

#include <iostream>

inline void oops_func() { std::cout << OOPS; }

А мы такие же махинации когда включаем и возимся с макросом

// tu1.cpp
#define OOPS 123
#include <oops_header.h>
void a() { oops_func(); }

// tu2.cpp
#define OOPS 42
#include <oops_header.h>
void b() { oops_func(); }

Где основная функция, подобная этой

extern void a();
extern void b();
int main() {
  a();
  b();
}

Будут отображаться те же проблемы, с которыми вы столкнулись с модулями. Вывод будет зависеть от звездочек, потому что я нарушил ODR (встроенные функции требуют от ODR идентичности на уровне токена).

Вы видите это с модулями из-за артефакта их сегодняшней реализации (что-то вроде предварительно скомпилированных заголовков).

И да, я изо всех сил старался сломать ODR «необнаруживаемым» способом, но это только потому, что нет необходимости его обнаруживать. Я нарушил соглашение, по которому компилятор доверял мне вести себя разумно.

StoryTeller - Unslander Monica 14.02.2023 09:47

Я действительно этого не знал. Это разочаровывает, но приятно знать. Спасибо

Basti 14.02.2023 09:50

Тем не менее, реализации C++ обычно предоставляют диагностику для многократно определенных невстроенных функций и переменных "старого C++" с внешней связью. Это регресс, независимо от того, что говорит стандарт.

n. m. 14.02.2023 12:31

@н.м. - Невозможно регрессировать, когда вещи раньше не существовало. TU и модульные блоки взаимодействуют... не совсем очевидным образом, я бы сказал.

StoryTeller - Unslander Monica 14.02.2023 12:32

@н.м. -- повторяющиеся имена в отдельных библиотеках обычно не вызывают сообщений об ошибках. Например, это обычный механизм замены глобальных operator new и operator delete.

Pete Becker 14.02.2023 15:29

@PeteBecker Это вообще не работает как замена глобального механизма создания/удаления. Если вы добавите void f0() { f_test(); } к module0 и void f1() { f_test(); } к module1 и вызовете f0 и f1 из main, f0 вызовет f_test из module0, f1 вызовет f_test из module1, а main вызовет тот f_test, который будет импортирован первым (аналогично Windows DLL). Это довольно прискорбно.

n. m. 14.02.2023 17:19

@н.м. -- Я говорил о том, как работает связывание в том, что вы называете "старым C++"; после разрешения символа компоновщики обычно игнорируют повторяющиеся определения в библиотечных модулях, которые иначе не были загружены.

Pete Becker 14.02.2023 17:53

@PeteBecker, если библиотечный модуль (объект на языке компоновщика) не вставлен, он просто не является частью программы, и ODR к нему не применяется.

n. m. 14.02.2023 18:27

@н.м. -- то есть в лучшем случае глянец; в стандарте ничего не говорится о библиотеках (в этом смысле), и связывание с библиотекой делает имена в этой библиотеке видимыми для компоновщика. В стандарте ничего не говорится о том, что компоновщик не должен сообщать об ошибке для такой программы. Здесь, конечно, много традиций, и любой компоновщик, который отказался бы связать эту программу, не был бы вообще популярен. <g> Но это далеко от вашего вопроса.

Pete Becker 14.02.2023 19:04

@PeteBecker В какой-то момент вам нужно решить, что является частью вашей программы, а что нет. Стандарт не диктует это решение. Если вы решите, что вся библиотека, которую вы передаете компоновщику, является частью программы, то вы должны избегать использования всех имен, определенных несвязанными частями этой библиотеки, иначе ваша программа будет иметь UB без какой-либо веской причины.

n. m. 14.02.2023 20:33

@н.м. -- как компилятор/компоновщик узнает, что вы решили? Обычно вы не указываете компоновщику, какие части библиотеки использовать; он просматривает всю библиотеку, чтобы увидеть, что полезно.

Pete Becker 14.02.2023 21:36

@PeteBecker Вы можете решить, что все, что на самом деле сделал компоновщик, является частью программы, а все, что не было, не является. (Вы знаете, как компоновщик загружает данные, это описано в руководстве; вы можете восстановить список вручную, если хотите). Или у вас может быть какая-то альтернативная процедура принятия решения. Я не знаю ни одного здравомыслящего.

n. m. 15.02.2023 04:21

Другие вопросы по теме