GMP для C++: больше проблем с auto

Подробнее о странном поведении Интерфейс класса GMP C++ , связанного с ключевым словом C++ auto. Мой предыдущий вопрос также был об этом ключевом слове, и его удаление решило мою проблему. Однако это была проблема компиляции, а теперь проблема во время выполнения.

Простой тест ниже компилируется нормально, но создается дамп памяти.

#include <iostream>
#include <gmpxx.h>

auto test()
{
  return mpz_class(2) * 2;
}

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

Если заменить ключевое слово auto на mpz_class, то тест пройдет корректно. Кроме того, если опустить флаг компиляции -O3, тест запустится, но выведет неправильное число, которое будет меняться при каждом выполнении (что обычно происходит, когда выполнение зависит от случайных неинициализированных данных).

Похоже, что заголовок gmpxx.h содержит что-то, из-за чего компилятор C++ в данном случае генерирует неверный код. Как нейтрализовать это нечто?

Возможный ответ: «Не используйте возвращаемый тип auto», однако я предпочитаю использовать его — это упрощает модификации.

Системная среда:

  • ОС: Ubuntu 22.04.4 LTS.
  • Компилятор: g++ (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
  • Флаги компилятора/компоновщика: -O3 -Wall -std=c++20 -lgmpxx -lgmp
  • Версия GMP: 6.2.1
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Документация по привязкам GMP C++ совершенно ясна: избегайте использования auto для выражений GMP. (На самом деле, даже обычные шаблоны могут вызвать проблемы).

Проблема в том, что GMP опирается на технику «шаблона выражения». Для mpz_class a, b это означает, что a + b не является самим mpz_class, а скорее объектом другого типа, который представляет сумму a и b без фактического ее вычисления. Такой объект обычно будет ссылаться на a и b по ссылке. Объект можно неявно преобразовать в mpz_class. Идея состоит в том, что этот метод позволяет специализировать функции в зависимости от функций, создавших их операнды. Например. вот игрушечный пример:

struct my_num { double num; };
template<typename... Ts>
struct my_num_sum { // expression template representing sums
    std::tuple<Ts&...> summands;
    operator my_num() {
        // pretend that there is a more efficient method to add together multiple values of my_num at once than just repeatedly using +
        return std::apply([](Ts&... summands) { return my_num((0 + ... + my_num(summands).num)); }, summands);
    }
};
auto operator+(my_num &a, my_num &b) {
    return my_num_sum<my_num, my_num>(std::tie(a, b));
}
// this specializes the addition operator to do something different (i.e. more efficient) when one of the operands is itself a sum
template<typename... Ts, typename U>
auto operator+(my_num_sum<Ts...> as, U &b) {
    return my_num_sum<Ts..., U>(std::tuple_cat(as.summands, std::tie(b)));
}

В этом примере для my_num a, b, c выражение a + b + c — это не my_num, а my_num_sum<my_num, my_num, my_num>. Строка my_num d = a + b + c складывает a, b и c одновременно (за один вызов my_num_sum::operator my_num()), что может быть более эффективным, чем сложение a и b в промежуточный объект, а затем добавление к нему c. (Это неверно для double, но имеет смысл для более крупных типов, таких как векторы).

Вы видите, что теперь существует большая опасность: пользовательская функция может легко создавать висячие ссылки:

auto oops(my_num a, my_num b) { return a + b; }
// returns a my_num_sum containing dangling references to parameters!

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

my_num fine(my_num a, my_num b) { return a + b; }

Если вы действительно настаиваете на использовании возвращаемых типов auto, хорошо, но теперь вам все равно нужно добавить приведения к my_num в тех местах, где вы не хотите, чтобы шаблоны выражений утекли.

auto okay(my_num a, my_num b) { return my_num(a + b); }

Это именно те два исправления, которые предлагаются в документации GMP для вашей проблемы. Чтобы правильно определить test, напишите одно из следующих слов:

mpz_class test() { return mpz_class(2) * 2; }
auto test() { return mpz_class(mpz_class(2) * 2); }

Вам нужно где-то сказать mpz_class, потому что в противном случае не будет ничего, что могло бы вызвать неявное преобразование шаблона выражения в число.

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