在同一个翻译单元中,ODR问题很容易诊断。 那么,为什么编译器不警告同一翻译单元中的 ODR 违规行为呢?
例如,在下面的代码 https://wandbox.org/permlink/I0iyGdyw9ynRgny6(下面转载)中,检测是否已定义std::tuple_size
时存在 ODR 冲突。 此外,当您取消注释three
和four
的定义时,未定义的行为很明显。 程序的输出将更改。
只是试图理解为什么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 保证模板的全名及其参数完全定义了它的含义,因此一旦实例化模板并生成"它是什么",您就可以存储从其名称(所有参数命名)到"它是什么"的表。
下次您向模板传递其参数时,它不会尝试再次实例化它,而是在记忆表中查找它。 如果找到,它将跳过所有这些工作。
为了执行所需的操作,编译器必须放弃此优化,并在每次传递模板参数时重新实例化模板。 这可能会导致构建时间大幅减慢。
在你的玩具代码中,也许不是,但在真正的项目中,你可以有数千个独特的模板和数十亿个模板实例化,记忆可以将模板实例化时间减少一百万倍。