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