hp
toc

Vanishing members

2022-01-22, post № 254

programming, #c++, #template-metaprogramming

Inheritance is all about keeping material possessions in the familly. Thus, a child may access non-private parent members:

#include <iostream>
struct A { int v{5}; };
struct B : A {
    void f() { std::cout << v << std::endl; } };
int main() { B{}.f(); }

Even though v has not been declared inside struct B’s declaration, it accesses it through its inheritance ties to struct A. Feeling the need to be polymorphic with regards to the numerical representation, struct A may be changed to a struct template:

#include <iostream>
template<typename T> struct A { T v{5}; };
struct B : A<int> {
    void f() { std::cout << v << std::endl; } };
int main() { B{}.f(); }

Yet hard-baking in the numerical type for the derived struct feels overly restrictive. As such, one might want to build a templated class hierarchy:

#include <iostream>
template<typename T> struct A { T v{5}; };
template<typename T> struct B : A<T> {
    void f() { std::cout << v << std::endl; } };
int main() { B<int>{}.f(); }

What surprised me nearly two weeks ago — when a templated class hierarchy naturally arose — is that the above does not compile.

$ clang++ 2.cpp
2.cpp:4:29: error: use of undeclared identifier 'v'
    void f() { std::cout << v << std::endl; } };
                            ^
1 error generated.

Intriguingly, the error Debian clang version 11.0.1-2 produces does not mention anything related to hierarchies — v is not a private member, is not incorrectly typed, it is not even shadowed: v apparently does not exist, despite me clearly seeing it defined in the second line.
Puzzled, as a first remedy I chose to indirectly refer to v via indirection through oneself: this->v. Surprisingly, this satisfied clang and lead to a successful compilation.

#include <iostream>
template<typename T> struct A { T v{5}; };
template<typename T> struct B : A<T> {
    void f() { std::cout << this->v << std::endl; } };
int main() { B<int>{}.f(); }

With now a compiling class hierarchy under my belt, I had overcome my standard’s tussle with clang. Though I kept wondering why clang apparently did not see what seemed so obvious to me: what motives did clang have to shroud v’s existence in darkness? Had I uncovered the remnents of a rivalry for RAM real estate? Had all the compilers sworn to conspire against my humble store of value? My innocent v?

With these thoughts still lingering in my mind, it was brought to my attention that inside the template instantiation the template engine may be brought to branch on certain types not to emit a declaration of v. And whilst initially the tought laid on transforming a pointer declaration into a multiplication, I later realised how expressions are forbidden from struct declarations. As such, another quirk of the template engine had to be found.
And find I did: Partial template specialization allows one to remove a declaration altogether on certain special types:

#include <iostream>
template<typename T> struct A { T v{5}; };
template<typename T> struct B : A<T> {
    void f() { std::cout << this->v << std::endl; } };
template<> struct A<int> { };
int main() { B<int>{}.f(); }

Making clear that the compiler cannot decide if v will exist after the template declaration of template struct B, since later template struct A could be specialized in such a way that it loses its member.

Extra assets: 0.cpp, 1.cpp, 2.cpp, 3.cpp, 4.cpp
Jonathan Frech's blog; built 2024/04/13 20:55:09 CEST