Строки в заголовках - это нарушает 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 не удовлетворяют эти требования, то поведение undefined.

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

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

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

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

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

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

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

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

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

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

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

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

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

Как правило, я думал, что lvalues имеют имена.

Ответ 1

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

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

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

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