Строки в заголовках - это нарушает ODR?

Рассмотрим следующую программу с двумя модулями компиляции.


// a.hpp

class A {
  static const char * get() { return "foo"; }
};

void f();

// a.cpp

#include "a.hpp"
#include <iostream>

void f() {
  std::cout << A::get() << std::endl;
}

// main.cpp

#include "a.hpp"
#include <iostream>

void g() {
  std::cout << A::get() << std::endl;
}

int main() {
  f();
  g();
}

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

У меня сложилось впечатление, что этот способ сделать это (показанный выше) с помощью функции "хорошо", потому что это inline Функция и компоновщик устраняет любые дубликаты, которые создаются, и программы, написанные с использованием этого шаблона, похоже, работают нормально. Однако теперь у меня есть сомнения относительно того, является ли это на самом деле законным.

Функция A::get используется odr в двух разных единицах перевода, но он неявно встроен, поскольку является членом класса.

В [basic.def.odr.6] Говорится:

В программе может быть несколько определений встроенной функции... с внешней связью (7.1.2)... при условии, что каждое определение появляется в другой единице перевода, и при условии, что определения удовлетворяют следующим требованиям. Учитывая такой объект по имени D определяется более чем в одной единице перевода, затем
- каждое определение D должен состоять из одинаковой последовательности токенов; а также
- в каждом определении Dсоответствующие имена, просмотренные в соответствии с 3.4, должны относиться к объекту, определенному в определении Dили должен ссылаться на ту же сущность после разрешения перегрузки (13.3) и после сопоставления частичной специализации шаблона (14.8.3), за исключением того, что имя может ссылаться на энергонезависимый объект const с внутренней или без связи, если объект имеет один и тот же буквальный тип во всех определениях Dи объект инициализируется константным выражением (5.19), и объект не используется odr, и объект имеет одинаковое значение во всех определениях D; а также
- в каждом определении Dсоответствующие субъекты должны иметь одинаковые языковые связи; а также
- ... (больше условий, которые не кажутся актуальными)

Если определения D удовлетворить все эти требования, то программа должна вести себя так, как если бы было одно определение D, Если определения D не удовлетворяют этим требованиям, то поведение не определено.

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

Однако не ясно, что второе условие выполнено. Потому что имя "foo" может не соответствовать одному и тому же объекту в двух единицах компиляции - это потенциально "разные" строковые литералы в каждом, нет?

Я попытался изменить программу:

  static const void * get() { return static_cast<const void*>("foo"); }

так что он печатает адрес строкового литерала, и я получаю тот же адрес, однако я не уверен, гарантировано ли это произойдет.

Подпадает ли оно под "... относится к субъекту, определенному в определении D"? Является "foo" считается определенным в A::get Вот? Может показаться, что это так, но, как я понимаю неофициально, строковые литералы в конечном итоге приводят к тому, что компилятор генерирует какую-то глобальную переменную. const char[] который живет в специальном сегменте исполняемого файла. Является ли эта "сущность" внутри A::get или это не актуально?

Является "foo" даже считается "имя", или термин "имя" относится только к действительному "++ " "идентификатору", как это может быть использовано для переменной или функции? С одной стороны это говорит:

[basic][3.4]
Имя - это использование идентификатора (2.11), идентификатора оператора-функции (13.5), идентификатора оператора-литерала (13.5.8), идентификатора функции преобразования (12.3.2) или идентификатора шаблона (14.2) который обозначает сущность или метку (6.6.4, 6.1).

и идентификатор

[lex.name][2.11]
Идентификатор - это произвольно длинная последовательность букв и цифр.

похоже, строковый литерал не является именем.

С другой стороны, в разделе 5

[expr.prim.general][5.1.1.1]
Строковый литерал является lvalue; все остальные литералы являются значениями.

Вообще я думал что lvalues есть имена.

1 ответ

Решение

Ваш последний аргумент - чепуха. "foo" даже не грамматически имя, а строковый литерал. И строковые литералы, являющиеся lvalues, и некоторые lvalues, имеющие имена, не подразумевают, что строковые литералы являются или имеют имена. Строковые литералы, используемые в вашем коде, не нарушают ODR.

На самом деле, до C++11 предписывалось, чтобы строковые литералы в нескольких определениях встроенных функций через TU обозначали одну и ту же сущность, но это избыточное и в основном невыполненное правило было удалено CWG 1823.

Потому что имя "foo" может не соответствовать одному и тому же объекту в двух единицах компиляции - это потенциально "разные" строковые литералы в каждом, нет?

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

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