Why doesn't the compiler warn against ODR violations in the same translation unit

273 views Asked by At

In the same translation unit, ODR problems are easy to diagnose. Why then does the compiler not warn against ODR violations in the same translation unit?

For example in the following code https://wandbox.org/permlink/I0iyGdyw9ynRgny6 (reproduced below), there is an ODR violation with detecting if std::tuple_size has been defined. And further the undefined behavior is evident when you uncomment the defintiions of three and four. The output of the program changes.

Just trying to understand why ODR violations are so hard to 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;
}
1

There are 1 answers

2
Yakk - Adam Nevraumont On BEST ANSWER

To make template compiling fast, compilers memoize them.

Because ODR guarantees that the full name of a template and its arguments fully defines what it means, once you instantiate a template and generate "what it is", you can store a table from its name (with all arguments named) to "what it is".

The next time you pass the template its arguments, instead of trying to instantiate it again, it looks it up in the memoization table. If found, it skips all of that work.

In order to do what you want, the compiler would have to discard this optimization and re-instantiate the template every time you passed it arguments. This can cause a massive slowdown of build times.

In your toy code, maybe not, but in a real project you could have thousands of unique templates and billions of template instantiations, the memoization can reduce template instantiation time by a factor of a million.