Могут ли мешать функции conteval из разных единиц перевода? - PullRequest
0 голосов
/ 18 октября 2019

Я пытаюсь понять значение функции inline и наткнулся на эту проблему. Рассмотрим эту маленькую программу ( demo ):

/* ---------- main.cpp ---------- */
void other();

constexpr int get()
{
    return 3;
}

int main() 
{
    std::cout << get() << std::endl;
    other();
}

/* ---------- other.cpp ---------- */
constexpr int get()
{
    return 4;
}

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

При компиляции без оптимизации программа выдает следующий вывод:

3
3

Что может быть не тем, что мыхочу, но, по крайней мере, я могу это объяснить.

  1. Компилятору не требуется вычислять результаты функций constexpr во время компиляции, поэтому он решил отложить его до времени выполнения.
  2. constexpr для функций подразумевает inline
  3. Наши get() функции имели разные реализации
  4. Мы не объявляли get() функции статическими
  5. Компоновщик должен выбрать только одну реализацию функции get()

И так получилось, что компоновщик выбрал get() из main.cpp, что вернул 3.

Теперь до той части, которую я не понимаю. Я просто изменил get() функции с constexpr на consteval. Теперь компилятор должен вычислять значение во время компиляции, то есть до времени компоновки (верно?). Я бы ожидал, что get() функций вообще не будет присутствовать в объектных файлах.

Но когда я его запускаю ( demo ), У меня точно такой же вывод ! Как это может быть? .. Я имею в виду да, я понимаю, что это неопределенное поведение, но это не главное. Почему значения, которые должны были быть вычислены во время компиляции, влияли на другие единицы перевода?

UPD: Мне известно, что эта функция указана как невыполненная в clang , но вопрос в любом случае применим. Разрешено ли совместимому компилятору демонстрировать такое поведение?

Ответы [ 3 ]

6 голосов
/ 18 октября 2019

Требование для функции consteval состоит в том, что каждый вызов ее должен создавать постоянное выражение.

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

Неопределенное поведение не определено.

5 голосов
/ 18 октября 2019

Программа с двумя определениями одной и той же встроенной функции является неправильно сформированной программой, диагностика не требуется.

Стандарт не устанавливает требований к поведению во время выполнения или компиляции некорректно сформированной программы.

Теперь в C ++ нет «времени компиляции», как вы его себе представляете. В то время как почти каждая реализация C ++ компилирует файлы, связывает их, создает двоичный файл, а затем запускает его, стандартные подсказки C ++ касаются этого факта.

В нем говорится о единицах перевода и о том, что происходит, когда вы объединяете их впрограмма и поведение этой программы во время выполнения.

...

На практике ваш компилятор может строить карту из символа в некоторую внутреннюю структуру. Он компилирует ваш первый файл, а затем во втором файле все еще обращается к этой карте. Новое определение той же встроенной функции? Просто пропустите это.

Во-вторых, ваш код должен создавать выражение постоянной времени компиляции. Но выражение константы времени компиляции не является наблюдаемым свойством в контексте, где вы его использовали, и нет никаких побочных эффектов при выполнении этого при ссылке или даже во время выполнения! И как если бы ничто не мешало этому.

consteval говорит: «если я запускаю это, и правила, которые допускают, чтобы это было постоянным выражением, нарушались, я должен ошибиться, а не отступить отпостоянное выражение ". Это похоже на «оно должно быть запущено во время компиляции», но это не то же самое.

Чтобы определить, что из этого происходит, попробуйте следующее:

template<auto x>
constexpr std::integral_constant< decltype(x), x > constant = {};

теперь замените ваши строки печати на:

std::cout << constant<get()> << std::endl;

, что делает нецелесообразным откладывать оценку на время выполнения / связывания.

Это будет отличать "умный компилятор откеширование get "from" компилятор оценивает его позже во время компоновки ", потому что определение того, какой ostream& << для вызова, требует создания экземпляра типа constant<get()>, что, в свою очередь, требует оценки get().

Compilersкак правило, не откладывайте разрешение перегрузки на время соединения.

1 голос
/ 18 октября 2019

Ответ на этот вопрос заключается в том, что это все еще нарушение ODR, независимо от того, является ли функция constexpr или consteval. Возможно, с конкретным компилятором и конкретным кодом вы можете получить ожидаемый ответ, но он все еще плохо сформирован, диагностика не требуется.

Что вы можете сделать, это определить их в анонимных пространствах имен:

/* ---------- main.cpp ---------- */
void other();

namespace {
    constexpr int get()
    {
        return 3;
    }
}

int main() 
{
    std::cout << get() << std::endl;
    other();
}

/* ---------- other.cpp ---------- */
namespace {
    constexpr int get()
    {
        return 4;
    }
}

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

Но еще лучше, просто используйте модули:

/* ---------- main.cpp ---------- */
import other;

constexpr int get()
{
    return 3;
}

int main() 
{
    std::cout << get() << std::endl; // print 3
    other();
}

/* ---------- other.cpp ---------- */
export module other;

constexpr int get() // okay, module linkage
{
    return 4;
}

export void other()
{
    std::cout << get() << std::endl; // print 4
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...