为什么编译器不会在同一个翻译单元中警告ODR违规

时间:2017-08-28 15:10:56

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

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

例如,在以下代码https://wandbox.org/permlink/I0iyGdyw9ynRgny6(下面转载)中,存在ODR违规,检测是否已定义std::tuple_size。当您取消注释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;
}

1 个答案:

答案 0 :(得分:6)

为了快速进行模板编译,编译器会记住它们。

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

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

为了做你想做的事,编译器必须放弃这个优化,并在每次传递参数时重新实例化模板。这可能会导致构建时间大幅减缓。

在您的玩具代码中,可能没有,但在真实项目中,您可以拥有数千个独特模板和数十亿个模板实例,这些内存可以将模板实例化时间缩短一百万倍。