【问题标题】:Different compiler behavior with C++11C++11 的不同编译器行为
【发布时间】:2015-01-23 15:47:46
【问题描述】:

以下代码

#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 上编译良好​​p>

  1. Debian clang 版本 3.5.0-9
  2. gcc 版本 4.9.1
  3. icpc 版本 15.0.1

在 Mac OS X 上

  1. gcc 版本 4.9.2

但没有

  1. clang-600.0.56
  2. icpc 版本 15.0.1

除非定义了宏 OK。我不知道哪些是错误的编译器,有人可以告诉我吗?谢谢。

PS:这里是错误

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

【问题讨论】:

  • 能把得到的编译错误加进去吗?
  • 我已经更新了我的第一篇文章以包含错误。我不明白为什么会出现这样的错误,因为对我来说,可以在语法分析期间评估测试第 8 行,因此编译器应该知道只有在 K = std::complex&lt;double&gt; 时才评估第 10 行,不是吗?跨度>
  • 整个函数还是要编译的。您正在尝试使用 if 作为(不存在的)static if。只需编写两个重载。 (remove_pointer 也没有意义。doublecomplex&lt;double&gt; 都不是指针。)
  • 对不必要的remove_pointer感到抱歉。为什么建议使用两个重载,如果我定义宏 OK,代码在所有平台上编译和运行 doublecomplex&lt;double&gt; 都很好,我猜这个实现的运行时成本不会高很多比有两个重载的?
  • 因为它更容易阅读,而且实际上更短。

标签: c++11 g++ complex-numbers clang++ icc


【解决方案1】:

不同之处在于,在 Linux 上,您使用的是 libstdc++ 和 glibc,而在 MacOS 上,您使用的是 libc++ 和任何 CRT MacOS 使用的。

MacOS 版本正确。 (此外,您的解决方法已完全失效且非常危险。)

这就是我认为会发生的事情。

环境中有多个conj 重载。 C++98 引入了一个模板,它接受 std::complex&lt;F&gt; 并返回相同的类型。因为这个模板需要推导F,所以在用简单的浮点数调用conj时它不起作用,所以C++11添加了conj的重载,它采用floatdouble和@ 987654328@,并返回适当的std::complex 实例化。

然后有一个来自 C99 库的全局函数 ::conj,它接受 C99 double complex 并返回相同的值。

据我所知,libstdc++ 尚未提供新的 C++11 conj 重载。未调用 conj 的 C++ 版本。然而,似乎::conj 以某种方式进入了std 命名空间,并被调用。您传递的 double 通过添加零虚部隐式转换为 double complexconj 否定该零。通过丢弃虚部,结果double complex 被隐式转换回double。 (是的,这是 C99 中的隐式转换。不,我不知道他们在想什么。)结果可以分配给z

libc++ 提供了新的重载。选择double 的那个。它返回一个std::complex&lt;double&gt;。这个类没有到double的隐式转换,所以对z的赋值会给你一个错误。

底线是:您的代码完全没有意义。 vector&lt;double&gt; 不是 vector&lt;complex&lt;double&gt;&gt; 并且不应被视为一个。在double 上调用conj 没有意义。要么它不编译,要么它是一个空操作。 (libc++ 的conj(double) 实际上是通过简单地构造一个虚部为零的complex&lt;double&gt; 来实现的。)而且疯狂地reinterpret_cast 解决编译错误是可怕的。

【讨论】:

  • 感谢您的回答。我忘了补充一点,此代码仅使用 K = doubleK = std::complex&lt;double&gt; 实例化(请参阅上面的更新代码)。查看 sn-p 的第 8 行测试,reinterpret_cast 仅用K = std::complex&lt;double&gt; 调用。您能否评论一下为什么 reinterpret_cast&lt;std::complex&lt;double&gt;*&gt;std::complex&lt;double&gt;* 是“可怕的”?我在想编译器可以忽略这个调用。
  • 那不会太可怕,只是丑陋,只是等待一些微妙的变化(也许切换到complex&lt;float&gt;?)让它再次变得可怕。 reinterpret_cast 很糟糕。最好将函数的complex 特定部分提取到一个单独的函数中,该函数有两个重载,一个用于double,一个用于complex&lt;double&gt;。这样您就不必解决抱怨无效操作的编译器。
  • OP 似乎遗漏了这样一个事实,即无法运行、仍可编译且必须有效的代码。一个涵盖可以改善答案的段落。
【解决方案2】:

Sebastian Redl 的回答解释了为什么您的代码没有使用 libc++ 编译但使用 libstdc++ 编译。 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); });
}

我知道我更喜欢哪一个。

【讨论】:

    猜你喜欢
    • 2011-02-09
    • 2021-05-03
    • 1970-01-01
    • 2013-12-08
    • 2013-10-19
    • 1970-01-01
    • 1970-01-01
    • 2013-05-16
    • 1970-01-01
    相关资源
    最近更新 更多