bolov bolov - 22 days ago 4x
C++ Question

Instantiation of ill-formed non-templated method of a templated class

I was researching on two phase name lookup. A very logical explanation shows that one of the main reasoning for it is that follows the C++ philosophy of catching errors as early as possible.

My question is why isn't this philosophy followed with non-templated methods. Instead of checking when and if the method is called, why not check all non-templated methods in phase 2 when the templated class is instantiated?


template <class T>
struct X {

auto foo() // non-templated (important)
T t{};
return t.non_existing();

int main()
X<int> x; // (1) this compiles OK.

// somewhere is a galaxy far far away,
// maybe deep inside some unrelated code; // (2) the error is here

If you never write (2) the program compiles and runs without any problems, although
is illegal for the instantiated

I think that the line (1) should generate error, regardless if you ever call
or not.

When writing templated classes, this can let slip through the cracks an error until you finally call that problematic method (2), instead of getting the error when you instantiate the templated class (1).

Also sanity check: is the code valid if I instantiate
(1) but never call
(2)? Or is it something like "ill-formed, no diagnostics required"? If the latter, then this is an even more reason to catch the error earlier.


The code is valid.

This feature was designed to permit things like std::vector having an operator< or operator== written simply.

This operator would try to call < or == on its elements, blindly. If it was invalid, once you called < or == on the wrapping vector it would fail to compile.

But if you never did, the vector would work fine.

Modern C++ would advise using SFINAE conditional method techniques or C++20 Requires clauses, so vector would only have a == or < if it was a vadid operation. None of these techniques where mature when vector was designed, and the ability to have methods of template classes that where not valid was an important feature.

On top of early-failure on invalid code, having == conditionally exist permits wrapping code to detect if == is safe to call: the ancient technique does not permit this introspection. I have had to write custom traits that specialize on the standard container templates to detect if < is safe to call in at least one case.