Другое поведение компилятора с C++11

Следующий код

#include <vector>
#include <complex>
#include <algorithm>

template<class K>
inline void conjVec(int m, K* const in) {
    static_assert(std::is_same<K, double>::value || std::is_same<K, std::complex<double>>::value, "");
    if(!std::is_same<typename std::remove_pointer<K>::type, double>::value)
#ifndef OK
        std::for_each(in, in + m, [](K& z) { z = std::conj(z); });
#else
        std::for_each(reinterpret_cast<std::complex<double>*>(in), reinterpret_cast<std::complex<double>*>(in) + m, [](std::complex<double>& z) { z = std::conj(z); });
#endif
}

int main(int argc, char* argv[]) {
    std::vector<double> nums;
    nums.emplace_back(1.0);
    conjVec(nums.size(), nums.data());
    return 0;
}

отлично компилируется в Linux с

  1. Дебиан лязг версии 3.5.0-9
  2. gcc версии 4.9.1
  3. icpc версия 15.0.1

и в Mac OS X с

  1. gcc версии 4.9.2

но не с

  1. лязг-600.0.56
  2. icpc версия 15.0.1

кроме случаев, когда макрос OK определен. Я не знаю, какие компиляторы неисправны, может кто-нибудь сообщить мне? Спасибо.

пс: вот ошибка

10:48: error: assigning to 'double' from incompatible type 'complex<double>'
        std::for_each(in, in + m, [](K& z) { z = std::conj(z); });

person BigDawg    schedule 23.01.2015    source источник
comment
Можете ли вы добавить ошибки компиляции, которые вы получаете?   -  person Angew is no longer proud of SO    schedule 23.01.2015
comment
Я обновил свой первый пост, чтобы включить ошибку. Чего я не понимаю, так это почему я получаю такую ​​ошибку, потому что для меня тестовая строка 8 может быть оценена во время синтаксического анализа, и поэтому компиляторы должны знать, что строка 10 оценивается только тогда, когда K = std::complex<double>, нет?   -  person BigDawg    schedule 23.01.2015
comment
Вся функция еще должна скомпилироваться. Вы пытаетесь использовать if как (несуществующий) static if. Просто напишите две перегрузки. (Кроме того, remove_pointer не имеет смысла. Ни double, ни complex<double> не являются указателями.)   -  person T.C.    schedule 23.01.2015
comment
Извините за ненужное remove_pointer. Почему вы рекомендуете использовать две перегрузки, если я определяю макрос OK, код компилируется и работает нормально как для double, так и для complex<double> на всех платформах, и я полагаю, что стоимость выполнения этой реализации не намного больше, чем с двумя перегрузками?   -  person BigDawg    schedule 23.01.2015
comment
Потому что его легче читать и он короче.   -  person T.C.    schedule 23.01.2015


Ответы (2)


Разница в том, что в Linux вы используете libstdc++ и glibc, а в MacOS вы используете libc++ и все, что использует CRT MacOS.

Версия MacOS правильная. (Кроме того, ваш обходной путь полностью сломан и безумно опасен.)

Вот что, я думаю, происходит.

В среде есть несколько перегрузок conj. C++98 вводит один шаблон, который принимает std::complex<F> и возвращает тот же тип. Поскольку для вывода этого шаблона требуется F, он не работает при вызове conj с простым числом с плавающей запятой, поэтому в C++11 добавлены перегрузки conj, которые принимают float, double и long double и возвращают соответствующий экземпляр std::complex.

Затем есть глобальная функция из библиотеки C99, ::conj, которая принимает double complex C99 и возвращает то же самое.

Насколько я понимаю, libstdc++ еще не предоставляет новые перегрузки C++11 conj. Версия C++ conj не вызывается. Однако оказывается, что каким-то образом ::conj попал в пространство имен std и был вызван. double, которое вы передаете, неявно преобразуется в double complex путем добавления нулевой мнимой части. conj отрицает этот ноль. Результат double complex неявно преобразуется обратно в double путем отбрасывания мнимой составляющей. (Да, это неявное преобразование в C99. Нет, я не знаю, о чем они думали.) Результат может быть присвоен z.

libc++ предоставляет новые перегрузки. Выбирается тот, кто получает double. Он возвращает std::complex<double>. Этот класс не имеет неявного преобразования в double, поэтому присваивание z выдает ошибку.

Суть в следующем: ваш код не имеет абсолютно никакого смысла. vector<double> не является vector<complex<double>> и не должно рассматриваться как таковое. Звонить conj по телефону double не имеет смысла. Либо не компилируется, либо не работает. (На самом деле conj(double) в libc++ реализовано путем простого построения complex<double> с нулевой мнимой частью.) И дико reinterpret_cast обходить ошибки компиляции ужасно.

person Sebastian Redl    schedule 23.01.2015
comment
Спасибо за ваш ответ. Я забыл добавить, что этот код создается только с помощью K = double или K = std::complex<double> (см. обновленный код выше). Глядя на тестовую строку 8 фрагмента, reinterpret_cast вызывается только с K = std::complex<double>. Не могли бы вы прокомментировать, почему ужасно reinterpret_cast<std::complex<double>*> и std::complex<double>*? Я думал, что этот вызов может быть исключен компилятором. - person BigDawg; 23.01.2015
comment
Это не было бы ужасно, просто уродливо, и просто нужно было бы немного изменить (может быть, переключиться на complex<float>?), чтобы снова сделать его ужасным. reinterpret_cast просто плохо. Было бы лучше выделить часть вашей функции, относящуюся к complex, в отдельную функцию, которая имеет две перегрузки: одну для double и одну для complex<double>. Тогда вам не придется обходить компилятор, жалующийся на недопустимые операции. - person Sebastian Redl; 23.01.2015
comment
Похоже, что в ОП отсутствует тот факт, что код, который не запускается, все еще компилируется и должен быть действительным. Параграф, посвященный улучшению ответа. - person Yakk - Adam Nevraumont; 26.01.2015

Ответ Себастьяна Редла объясняет, почему ваш код не скомпилировался с libС++, а с libstdС++. if — это не static if, существующее в некоторых языках; даже если код в ветке if мертв на 100%, он все равно должен быть действительным кодом.

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

Сравнивать:

template<class K>
inline void conjVec(int m, K* const in) {
    static_assert(std::is_same<K, double>::value || std::is_same<K, std::complex<double>>::value, "");
    if(!std::is_same<K, double>::value)
        std::for_each(reinterpret_cast<std::complex<double>*>(in), reinterpret_cast<std::complex<double>*>(in) + m, [](std::complex<double>& z) { z = std::conj(z); });
}

с:

inline void conjVec(int m, double* const in) {}
inline void conjVec(int m, std::complex<double>* const in) {
    std::for_each(in, in + m, [](std::complex<double>& z) { z = std::conj(z); });
}

Я знаю, какой из них я бы предпочел.

person T.C.    schedule 23.01.2015