为什么编译器不针对同一翻译单元中的 ODR 违规发出警告
Why doesn't the compiler warn against ODR violations in the same translation unit
在同一个翻译单元中,ODR问题很容易诊断。为什么编译器不警告同一翻译单元中的 ODR 违规?
例如,在下面的代码 https://wandbox.org/permlink/I0iyGdyw9ynRgny6(转载如下)中,检测是否已定义 std::tuple_size
时存在 ODR 违规。此外,当您取消注释 three
和 four
的定义时,未定义的行为是显而易见的。程序的输出改变了。
只是想了解为什么 ODR 违规如此难以解决 catch/diagnose/scary。
#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 保证模板的全名及其参数完全定义了它的含义,一旦实例化模板并生成 "what it is",您可以从其名称中存储一个 table (将所有参数命名为) "what it is".
下次您将模板传递给它的参数时,它不会尝试再次实例化它,而是在记忆 table 中查找它。如果找到,它将跳过所有这些工作。
为了执行您想要的操作,编译器必须放弃此优化并在您每次向模板传递参数时重新实例化模板。这可能会导致构建时间大幅减慢。
在您的玩具代码中,也许不是,但在实际项目中,您可能拥有数千个独特的模板和数十亿个模板实例化,记忆化可以将模板实例化时间减少一百万倍。
在同一个翻译单元中,ODR问题很容易诊断。为什么编译器不警告同一翻译单元中的 ODR 违规?
例如,在下面的代码 https://wandbox.org/permlink/I0iyGdyw9ynRgny6(转载如下)中,检测是否已定义 std::tuple_size
时存在 ODR 违规。此外,当您取消注释 three
和 four
的定义时,未定义的行为是显而易见的。程序的输出改变了。
只是想了解为什么 ODR 违规如此难以解决 catch/diagnose/scary。
#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 保证模板的全名及其参数完全定义了它的含义,一旦实例化模板并生成 "what it is",您可以从其名称中存储一个 table (将所有参数命名为) "what it is".
下次您将模板传递给它的参数时,它不会尝试再次实例化它,而是在记忆 table 中查找它。如果找到,它将跳过所有这些工作。
为了执行您想要的操作,编译器必须放弃此优化并在您每次向模板传递参数时重新实例化模板。这可能会导致构建时间大幅减慢。
在您的玩具代码中,也许不是,但在实际项目中,您可能拥有数千个独特的模板和数十亿个模板实例化,记忆化可以将模板实例化时间减少一百万倍。