Рассмотрим следующую программу с двумя единицами компиляции.
// 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 имеют имена.