Модули C Перенаправляют Объявление Сущности из другого модуля

#c #c 20 #forward-declaration #c -modules #gcc11

Вопрос:

Недавно я пытался преобразовать кодовую базу в модули C 20 с помощью GCC 11. Однако я застрял в следующей ситуации. Во-первых, вот как это было сделано с использованием заголовков:

A. h

 class B;

class A {
    public:
        void f(Bamp; b);
};
 

A.cpp

 #include "A.h"
#include "B.h"

void A::f(Bamp; b)
{
    // do stuff with b
}
 

(содержание B. h здесь не важно)

Следует отметить, что прямое объявление B. Не все, кто использует A, должны заботиться о B, поэтому я использовал прямое объявление, чтобы предотвратить повторную компиляцию. С заголовками эта ситуация работает идеально.

Проблема возникает при попытке преобразовать этот код в модули. Основная проблема заключается в том, что сущности привязаны к модулю, в котором они объявлены, поэтому прямое объявление в A. h невозможно. Я попытался объявить вперед в глобальном модуле, но затем компилятор все равно пожаловался, что определение B находится в другом модуле, чем его объявление. Я также попытался создать третий модуль, который просто содержал прямое объявление B, но это все еще объявляло и определяло B в двух разных модулях. Итак, мой главный вопрос: как я могу перенаправить объявление чего-либо из модуля за его пределами? Я также был бы удовлетворен способом, который в конечном итоге дает тот же эффект: пользователям A не нужно перекомпилироваться при изменении B.

Во время поиска я нашел несколько мест, где говорилось о похожих ситуациях, но все они по какой-то причине не сработали. Причины, по которым они не сработали:

  • Некоторые говорили, что у них есть модуль с прямыми объявлениями. Как я уже сказал выше, это не работает.
  • Некоторые говорили, что используют объявленные декларации о собственности. Однако они были удалены из окончательного стандарта C .
  • Некоторые говорили, что используют разделы модулей. Однако это работает только в том случае, если A и B находятся в одном модуле. A и B не должны находиться в одном модуле, поэтому это не работает.

Изменить: в ответ на комментарий, вот подробная информация о некоторых вещах, которые я пробовал:

Попытка 1: Перенаправить объявление B в A. mpp

A. mpp

 export module A;

class B;

export class A {
    public:
        void f(Bamp; b);
};
 

B.mpp

 export module B;

export class B {};
 

A.cpp

 module A;

import B;

void A::f(Bamp; b)
{
    // do stuff with b
}
 

When doing this, gcc errors with

 A.cpp:4:11: error: reference to ‘B’ is ambiguous
    4 | void A::f(Bamp; b)
      |           ^
In module B, imported at A.cpp:2:
B.mpp:3:14: note: candidates are: ‘class B@B’
    3 | export class B {};
      |              ^
In module A, imported at A.cpp:1:
A.mpp:3:7: note:                 ‘class B@A’
    3 | class B;
 

Attempt 2: Forward declare in new module

B_decl.mpp

 export module B_decl;

export class B;
 

A.mpp

 export module A;

import B_decl;

export class A {
    public:
        void f(Bamp; b);
};
 

B.mpp

 export module B;

import B_decl;

class B {};
 

A.mpp

 module A;

import B;

void A::f(Bamp; b)
{
    // do stuff with b
}
 

When doing this, gcc errors with

 B.mpp:5:14: error: cannot declare ‘class B@B_decl’ in a different module
    5 | class B {};
      |              ^
In module B_decl, imported at B.mpp:3:
B_decl.mpp:3:14: note: declared here
    3 | export class B;
 

Попытка 3: Прямое объявление в заголовке, определение в модуле

B_decl.h

 class B;
 

A. mpp

 module;

#include "B_decl.h"

export module A;

export class A {
    public:
        void f(Bamp; b);
};
 

B. mpp

 module;

#include "B_decl.h"

export module B;

class B {};
 

A.cpp

 module A;

import B;

void A::f(Bamp; b)
{
    // do stuff with b
}
 

При этом gcc выдает ошибки с

 B.mpp:7:7: error: cannot declare ‘class B’ in a different module
    7 | class B {};
      |       ^
In file included from B.mpp:3:
B_decl.h:1:7: note: declared here
    1 | class B;
 

Комментарии:

1. » но это все равно было объявлением и определением B в двух разных модулях » Откуда ты это знаешь? Какие ошибки вы получали? Кроме того, можете ли вы показать нам код для всех случаев использования модулей, которые вы пробовали? Вы использовали разделы модулей для этих объявлений или нет?

2. Я собирался начать тестовые преобразования из заголовочных файлов в модули, но это серьезная проблема. Почти все вещи, которые я хочу разделить, имеют зависимости, требующие прямого объявления.

Ответ №1:

Решение для этого зависит от того, почему вы хотите переслать объявление в первую очередь.

Если вы делаете это, чтобы разорвать циклическую зависимость, то обычно решение состоит в том, чтобы просто поместить их в один и тот же модуль. Поскольку компоненты так тесно связаны друг с другом, имеет смысл разместить их в одном модуле.

Если вы делаете это для ускорения компиляции, вам лучше просто импортировать модуль и использовать тип. Импорт модуля практически не требует затрат. Компиляция модуля уже выполнена, и это делается только один раз.

Комментарии:

1. Я делаю это для того, чтобы файлы не перекомпилировались так часто. Проблема с импортом заключается в том, что это приводит к тому, что все, что использует A, должно быть перекомпилировано при изменении B. Импорт модулей в основном бесплатен, но перекомпиляция файлов-нет.

2. @sudgy: Если вы знаете , что ничто в A интерфейсе модуля не зависит от реализации класса B , вы, вероятно, могли бы пропустить перекомпиляцию A клиентов, хотя это, очевидно, может быть неудобно для автоматизированных систем сборки, и некоторые компиляторы могут намеренно предотвратить это для обнаружения ошибок.

Ответ №2:

Я предполагаю, что это связано с привязкой модуля. При пересылке объявления B это имя по умолчанию имеет связь с модулем. Поэтому B он отличается от того, который определен в вашем модуле B (следовательно, неопределенный тип). Если вы экспортируете прямое объявление, вы получите внешнюю, а не модульную связь, которая должна решить вашу проблему.

На практике это означает изменение A.mpp в вашей 1-й попытке

 export module A;

export class B;

export class A {
    public:
        void f(Bamp; b);
};
 

вектор-оф-бул написал интересный пост на эту тему, если вы хотите узнать больше.

Комментарии:

1. GCC утверждает обратное. Это все еще дает ту же самую ошибку в этом, говоря, что ссылка на B неоднозначна.

2. хм, я тестировал с MSVC 16.11.

3. Похоже, что GCC прав в этой ситуации. От eel.is/c черновик/базовый.ссылка № 10 : «Если два объявления сущности прикреплены к разным модулям, программа плохо сформирована». И объявления прикрепляются к модулю, в котором они находятся ( eel.is/c проект/модуль#блок-7 ), в результате чего в разных модулях будет два объявления B , независимо от наличия export .

Ответ №3:

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

 // A_impl.cc

export module A_impl;

export template <typename B> class A_impl {
    public:
        void f(Bamp; b) {}
};
 
 // B.cc

export module B;

import A_impl;

export class B;

typedef A_impl<B> A;

export class B {
    public:
        void f(Aamp; a) {}
};
 
 // A.cc

export module A;

export import A_impl;
import B;

export typedef A_impl<B> A;
 
 // main.cc

import A;
import B;

int main(void) {
    A a;
    B b;

    a.f(b);
    b.f(a);

    return 0;
}
 

На данный момент clang не поддерживает разделы модулей, поэтому с помощью этой цепочки инструментов это, по-видимому, единственный способ определить A и B в разных файлах (без #include ) при размещении их в модулях.