c++ - 为什么编译器不针对同一翻译单元中的 ODR 违规发出警告

标签 c++ templates c++17 one-definition-rule

在同一个翻译单元中,ODR 问题很容易诊断。为什么编译器不警告同一翻译单元中的 ODR 违规?

例如在下面的代码中https://wandbox.org/permlink/I0iyGdyw9ynRgny6 (转载如下),检测是否定义了 std::tuple_size 存在 ODR 违规。此外,当您取消注释 threefour 的定义时,未定义的行为就很明显了。程序的输出改变了。

只是想了解为什么 ODR 违规如此难以发现/诊断/可怕。


#include <cstdint>
#include <cstddef>
#include <tuple>
#include <iostream>

class Something {
public:
    int a;
};

namespace {
template <typename IncompleteType, typename = std::enable_if_t<true>>
struct DetermineComplete {
    static constexpr const bool value = false;
};

template <typename IncompleteType>
struct DetermineComplete<
        IncompleteType,
        std::enable_if_t<sizeof(IncompleteType) == sizeof(IncompleteType)>> {
    static constexpr const bool value = true;
};

template <std::size_t X, typename T>
class IsTupleSizeDefined {
public:
    static constexpr std::size_t value =
        DetermineComplete<std::tuple_size<T>>::value;
};
} // namespace <anonymous>

namespace std {
template <>
class tuple_size<Something>;
} // namespace std

constexpr auto one = IsTupleSizeDefined<1, Something>{};
// constexpr auto three = IsTupleSizeDefined<1, Something>::value;

namespace std {
template <>
class tuple_size<Something> : std::integral_constant<int, 1> {};
} // namespace std

constexpr auto two = IsTupleSizeDefined<1, Something>{};
// constexpr auto four = IsTupleSizeDefined<1, Something>::value;

int main() {
    std::cout << decltype(one)::value << std::endl;
    std::cout << decltype(two)::value << std::endl;
}

最佳答案

为了使模板编译速度更快,编译器会内存它们。

因为 ODR 保证模板的全名及其参数完全定义了它的含义,一旦你实例化了一个模板并生成了“它是什么”,你就可以将一个表从它的名字(所有参数都命名)存储到“它是什么”。

下次您将模板传递给它的参数时,它不会尝试再次实例化它,而是在内存表中查找它。如果找到,它会跳过所有这些工作。

为了执行您想要的操作,编译器必须放弃此优化并每次您向模板传递参数时重新实例化模板。这可能会导致构建时间大幅减慢。

在您的玩具代码中,也许不是,但在实际项目中,您可能拥有数千个独特的模板和数十亿个模板实例化,记忆化可以将模板实例化时间减少一百万倍。

关于c++ - 为什么编译器不针对同一翻译单元中的 ODR 违规发出警告,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45922191/

相关文章:

c++ - 包含 malloc 声明的头文件的名称是什么?

c++ - 带有 C++11 线程的 Qtcreator

c++ - 为什么现代编译器没有消除对表达式模板的需求?

c++ - 在 C++17 中定义可变坐标(元组)类型?

c++ - std::set 需要多少额外内存(如果有的话)来存储它的元素 v.s.一个 std::vector?

c++ - 使用共享指针时释放后堆的使用

c++ - C++:检测 vector 在函数中是否为<complex>类型

c++ - 如何编码和重载返回自身的函数? C++17

c++ - 为在循环中多次调用的函数返回值预分配内存的正确方法是什么?

c++ - 将复杂的非原始 C++ 数据类型转换为 Erlang/Elixir 格式,以使用 NIF 导出方法