0

Does C++20 offer any new solutions to the problem of public member invisibility and source code bloat/repetition with inherited class templates described in this question over 2 years ago ?

cigien
  • 57,834
  • 11
  • 73
  • 112
George Robinson
  • 1,500
  • 9
  • 21
  • 5
    AFAIK, C++20 does not address this. And I'll be surprised if a future standard does either. The concern you describe is a direct consequence of rules related to lookup of dependent versus non-dependent names, and the type of solution you seek would require substantial changes of those rules, in a way that is likely to either break or (worse) change behaviour of older code. – Peter Oct 20 '20 at 00:45
  • 4
    The public members don't become "invisible" - you just need to qualify names. Adding `this->`'s doesn't really strike as "source code bloat", and is not at all what I thought this question would be about given the term "bloat"... – Barry Oct 20 '20 at 01:45
  • @Barry. Searching for 100s of places to add `this->` and adding these 6 characters 100s of times strikes me as Code Bloat and Repetition when I have to templatize a base class....especially when that added code adds NO new functionality ! – George Robinson Oct 20 '20 at 07:46
  • @Peter: What would an improvement to the solution #3 break? For example, like this?: `using CBase::*` or `using CBase::all_public` or `using CBase::all_protected` – George Robinson Oct 20 '20 at 09:17
  • @GeorgeRobinson - Do you actually understand what dependent name lookup is? – Peter Oct 20 '20 at 13:30
  • @Peter: Yes, I learned this almost 10 years ago when my friend had to add 100s of `this->` to his code and wrote this question: https://stackoverflow.com/questions/4643074/why-do-i-have-to-access-template-base-class-members-through-the-this-pointer. Since C++17 we are writing `using...` instead of `this->` because it is much less work, but still too much repetition for no added functionality. – George Robinson Oct 20 '20 at 22:45

1 Answers1

1

The "Problem"

The alleged "problem" is that, in a template, an unqualified name used in a way which isn't dependent on the specialization is truly independent of the specialization and refers to the entity with that name found at that point. The alleged source code "bloat" is using this-> to explicitly make the name dependent or qualifying the name. This is still the situation in C++20.

Just to be clear, the set of entities is not known at the point we refer to them. In the linked question, the base class depends on the template parameter, and we have only seen the primary base class template. The base class template may be specialized later and may have completely different member functions than the ones we've seen. So, any "solution" requires that a name without any obvious contextual dependence on the specialization find entities not yet declared which may be surprising.

Why It's Impossible

Any naive changes in this direction are either pretty big or have severe downsides, or both.

You could postpone all name lookup until instantiation time, discarding two phase lookup. That invites ODR violations which is silent UB, which is a huge downside.

You could restrict specialization so that you cannot specialize the base class later such that a different entity is found. That is difficult to diagnose, so it would likely be a new rule introducing silent UB.

You could opt in with a using declaration: using X::* as you propose or using class X as someone else suggested in a different context. This has the benefit of explicitness. It moves the problem a level up: if X is not dependent, presumably it should be found now, but if it is dependent, what happens? We can't instantiate it prior to instantiating the template we're in now. Thus, we can't interpret any names we see until instantiation. It has similar downsides to discarding two phase lookup.

Any of these changes would add complexity to an already complex area and would also pose significant backwards compatibility hurdles. None of them is a clear win.

Note: In C++20 they did make the rules more uniform by allowing ADL to find function templates with specified parameters to be found: f<int>(1).

Why It's Not a Problem

I doubt there would be consensus that this really is a problem. The linked question makes a poor argument. The derived class adds member function behavior to a certain base class, but a free function works better. These behaviors did not need member access, they aren't required by the language to be members, they can be found as non-members with ADL, and by using a free function they apply even when the static type you have is the base type. So, using inheritance for this is unnecessary coupling, and is a worse option.

Searching for 100s of places to add this-> and adding these 6 characters 100s of times strikes me as Code Bloat and Repetition when I have to templatize a base class

"Searching": The compiler will tell you when a name can't be found, which is better than silent bad behavior, such as making ODR violations easier to hit.

"templatize a base": Templatizing the base class doesn't trigger this. Templatizing the derived class and making the base dependent does. Yes, when templatizing the derived class, specifying that a bare name used in a way that is independent of the template parameter is in fact dependent may seem like boilerplate to some, but others might argue being explicit is clearer.

"100s of times": Seems hyperbolic.

These code patterns are used all the time in real world. Just look at CRTP. (comment on linked question)

Again, this only applies if the derived class is templated. I would dispute the commonality, but these idioms do exist and have a place.

Most importantly, though, is that CRTP is not a goal. CRTP is a hack. It's a C++ idiom because C++ lacks better facilities. CRTP allows a class to opt into certain behaviors that would otherwise be bothersome to write. Relevant C++ proposals do exist, but by and large, they have focused on making extension easier or removing boilerplate, and not on making CRTP, the hack, easier.

These are some that come to mind:

C++20: Comparisons

A very common use of CRTP is for things that require a lot of extra boilerplate. C++ required you to define operator== and operator!=, for instance. By opting into a CRTP base class, one could define only the primitive operation and have the other one generated.

C++20 fixed the underlying problem with comparisons. The typical member-wise comparison can be defaulted, and comparisons can be re-written so that != can invoke ==.

The problem is solved at the root, removing CRTP, not enhancing it.

C++20: Iterators to Ranges

Another common use of CRTP in the same vein as above is iterators. Writing custom iterators requires only a few fundamental operations: advance and dereference for forward iterators. But then there's a lot of extra seemingly unnecessary ceremony: pre-increment, post-increment, const iterators, typedefs, etc.

C++20 took a large step forward by introducing range concepts and the range library. The result is that it should be much less necessary to write a custom iterator. Ranges become a capable concept on their own, and there's a good suite of range combinators.

C++20: Concepts

C++ essentially has two systems for specifying an interface: virtual functions and concepts. They have trade-offs. Virtual functions are intrusive. But prior to C++20, concepts were implicit and emulated. One reason to use CRTP or inheritance generally would be to inject virtual functions. But in C++20, concepts are a language feature removing one big negative.

Future C++: Metaclasses

One value of CRTP in addition to the boilerplate reduction is satisfying an entire collection of multiple type requirements. A comparable class defines all the comparison operators. An clonable base defines a virtual destructor and clone.

This is the topic of metaclasses, which is not yet in C++.

See Also

See also the work on customization points which seems very interesting. And see the debates on unified function call syntax, which seems like we'll never get.

Summary

There's a very good question hiding in here about how C++20 makes it easier to reduce boilerplate, remove hacks like CRTP, and write better and clearer code. C++20 takes several steps in this regard, but they made the expression of intent easier, not a particular idiom.

Jeff Garrett
  • 5,863
  • 1
  • 13
  • 12