Генерация предупреждения при вызове функции-члена для временного объекта

Учитывая класс шаблона матрицы mat<M,N,T>, следующая функция-член позволяет мне эффективно транспонировать вектор-строку или вектор-столбец, поскольку они имеют одинаковый/соответствующий объем памяти:

template<int M, int N=M, typename T = double>
struct mat {
    // ...
    template<int Md = M, int Nd = N, typename = std::enable_if_t<Md == 1 || Nd == 1>>
    const mat<N, M, T>& transposedView() const {
        static_assert(M == 1 || N == 1, "transposedView() supports only vectors, not general matrices.");
        return *reinterpret_cast<const mat<N, M, T>*>(this);
    }
}

Я использовал эту функцию в течение многих лет и по привычке начал вызывать ее для временных выражений (/*vector-valued expression*/).transposedView(), забывая, что тогда она вернет ссылку на временное и приведет к неопределенному поведению, из-за которого GCC меня просто укусил.

Есть ли простой способ добавить что-то, что будет генерировать какое-то предупреждение, когда я вызываю его временно?

Или на самом деле должно быть безопасно вызывать его временно, пока я не сохраняю ссылку?


person Museful    schedule 29.07.2021    source источник
comment
Стратегия имеет неопределенное поведение. Это удача, что он не перестал работать или случайно не сломал вашу программу.   -  person François Andrieux    schedule 29.07.2021
comment
Вы можете перегружать lvalueness/rvalueness для функций-членов класса. Вы можете объявить и удалить версию rvalue.   -  person Nathan Pierson    schedule 29.07.2021
comment
@FrançoisAndrieux Даже если я не сохраняю возвращенную ссылку, а использую ее сразу?   -  person Museful    schedule 29.07.2021
comment
@Museful Да. Вы создаете указатель на mat<N, M, T>, где такого объекта нет (если только M и N оба не равны 1). Затем разыменование этого указателя является нарушением правил псевдонимов типов. В основном компилятор предполагает, что возвращенный указатель указывает на объект, отличный от this, потому что они указывают на разные типы. Иногда это не вызывает проблем, но компилятор может нарушить поведение вашего кода при попытке оптимизации.   -  person François Andrieux    schedule 29.07.2021
comment
@FrançoisAndrieux А если я никогда не вызываю функцию для временного объекта, а только для l-значений, это все еще UB?   -  person Museful    schedule 29.07.2021
comment
@Museful Да, разыменование этого reinterpret_cast в этом случае автоматически является Undefined Behavior (опять же, если mat<N,M,T> не имеет того же типа, что и mat<M,N,T>). Как правило, если reinterpret_cast кажется, что он решает проблему, скорее всего, это не так. В большинстве случаев единственное, что вы можете с ним сделать, — это вернуть объекту его истинный первоначальный тип.   -  person François Andrieux    schedule 29.07.2021
comment
@FrançoisAndrieux mat<N,1,T> и mat<1,N,T> содержат только std::array<T,N> и никаких других элементов данных. Это все еще УБ?   -  person Museful    schedule 29.07.2021
comment
Ага. Тем не менее, UB.std::complex имеет аналогичную проблему, поскольку массивы комплексных чисел обычно передаются в/из библиотек C. Таким образом, стандартная библиотека обходит UB, объявляя его неприменимым к std::complex в некоторых случаях.   -  person doug    schedule 29.07.2021
comment
Да. Я связал правила ранее.   -  person François Andrieux    schedule 29.07.2021
comment
@FrançoisAndrieux Будет ли это UB, если я использую актерский состав в стиле C? (Мне интересно, почему тогда reinterpret_cast вообще называется этим именем.)   -  person Museful    schedule 29.07.2021
comment
Еще УБ. Приведение в стиле C будет точно эквивалентно любому приведению C++, которое оно заменяет. Вы не можете сохранить эту стратегию.   -  person François Andrieux    schedule 29.07.2021
comment
@FrançoisAndrieux Спасибо за ваше терпение и подтверждения. Итак, если я правильно понял, С++ не дает мне возможности интерпретировать память, содержащую один POD, как другой POD.   -  person Museful    schedule 29.07.2021
comment
@Museful Нет, в C ++ для этого нет переносимого механизма. Вы должны memcpy (если типы действительно POD). В большинстве случаев компилятор оптимизирует memcpy, но я не думаю, что в вашем случае такая оптимизация будет вероятной.   -  person François Andrieux    schedule 29.07.2021


Ответы (1)


Функция-член может быть квалифицирована для объектов lvalue или rvalue. Используя это, вы можете создать набор перегрузок, например

template<int M, int N=M, typename T = double>
struct mat {
    // ...
    template<int Md = M, int Nd = N, typename = std::enable_if_t<Md == 1 || Nd == 1>>
    const mat<N, M, T>& transposedView() & const {
        static_assert(M == 1 || N == 1, "transposedView() supports only vectors, not general matrices.");
        return *reinterpret_cast<const mat<N, M, T>*>(this);
    }
    template<int Md = M, int Nd = N, typename = std::enable_if_t<Md == 1 || Nd == 1>>
    const mat<N, M, T>& transposedView() && const = delete;
}

и теперь, если вы попытаетесь вызвать функцию с объектом rvalue, вы получите ошибку компилятора.

person NathanOliver    schedule 29.07.2021
comment
Оказывается, даже вызов его для l-значений технически является UB. Но вы ответили именно на тот вопрос, который я задал. - person Museful; 29.07.2021