r/cpp 2d ago

On forcing all derived classes to implement a specific non-virtual method, part 2

https://devblogs.microsoft.com/oldnewthing/20260828-00/?p=112654
61 Upvotes

33 comments sorted by

31

u/Electronic_Tap_8052 2d ago

Huh. I usually accomplish this with a template type that has a function declaration but no implementation. That way there's no 'base class' at all to worry about, and it's all resolved at compile time.

I call it poor man's inheritance.

22

u/eyes-are-fading-blue 2d ago

Or you can define a concept.

8

u/Electronic_Tap_8052 2d ago

yeah you could nowadays. if im not mistaken though everything you can do with concepts could already be done with template fuckery

-6

u/eyes-are-fading-blue 2d ago

You can’t program with concepts. For example, you cannot reverse elements of a tuple.

4

u/ReversedGif 2d ago

'A is a subset of B' doesn't imply 'A = B' or 'B is a subset of A'.

1

u/eyes-are-fading-blue 2d ago edited 2d ago

I misread what they said. I thought they said you can do everything with concepts that you can do with templates which isn’t true.

1

u/Eric848448 2d ago

The syntax for those breaks my brain.

16

u/Alduish 2d ago

But why ?

Virtual classes have a reason to exist, if you cast the derived object back to the base class (a function that can opperate on any derived objects of this virtual class) then if the function isn't virtual you're gonna call the deleted function and the whole trick will have been useless.

14

u/LB-- Professional+Hobbyist 2d ago

I think the context is primarily for C++/WinRT, which does the virtual polymorphism stuff only on the concrete classes using CRTP, in order to properly set up the vtables for compatibility with COM's C ABI.

2

u/SubstituteCS 1d ago

Virtual method overhead.

For example, I have a pointer library that I wrote that wraps arbitrary addresses known to be valid of T.

These pointers have some variation in their reset and release methods.

It would be very stupid to use virtual methods for reset and release as each pointer would become the size of two pointers as the class would store both the pointer we are wrapping, and a pointer to the virtual method table.

-1

u/pjmlp 2d ago

Because many people go out of their way avoiding C++ features, at which point I always tell them just to use C, if they dislike C++ so much.

12

u/_Fibbles_ 2d ago

I very much want to be a fly on the wall when you tell Raymond Chen that he should use C because he clearly dislikes C++.

-2

u/pjmlp 1d ago

If you look into MFC versus OWL or VCL, the Microsoft C++ culture really clearly wants to write C with Classes and nothing else.

One needs to look into .NET for what Borland was doing with C++ already in Turbo Vision.

A spirit that permutates among the countless reboots of COM frameworks, versus C++ Builder as well.

Sure, are you arranging the meeting?

5

u/Gungan_Boss_Nass 2d ago

Tweaking the implementation to provide better compiler error messages is another example of compiler error message metaprogramming, which is one of the under-appreciated aspects of authoring a code library.

Possible errors is an interesting sub-category here. I can write requires or static_assert and provide an escape hatch for the caller to bypass it if it's not an error. That covers the 95% probability cases, but feels excessive for the 70% probability cases.

[[deprecated("This is probably wrong")]] with some SFINAE works better for those. You can build and test and only turn off the warning when you're really sure.

u/NilacTheGrim 2h ago

Meh. Kinda neat, but not sure how i feel about this.

If you really want to enforce some method be implemented in a certain set of related classes, then presumably you are polymorphic somewhere in some way in your code -- whether this be through traditional polymorphism via vtables, or polymorphism via templates.

If you're using vtables you don't need this technique since in that case you just do the usual virtual void Convert() = 0.

If you're using templates to achieve the polymorphism then you also don't need this because you can use concepts and/or requires clauses to enforce this.

But I guess the usage here is more succinct although it sort of makes the code weirder to maintain -- you have code that CONSUMES these classes that cares about the .Convert() method existing -- but they don't enforce that.

Instead you put the onus on the base class to enforce that "for reasons" that aren't immediately clear when reading the base class.

I would rather just use a concept or a requires clause at the CONSUMING end of this class hierarchy closer to the code that cares whether .Convert() exits or not... it's easier to maintain and reason about if you ask me.

But this is neat.. I'll give Mr. Chen that.

-1

u/Ateist 2d ago edited 2d ago

winrt::Windows::UI::Xaml::Interop::TypeName const&

WTF is this coding style?!

Examples are extremely hard to read due to multi-layered namespace issues like this.

This is well past the point where it serves its purpose and into the "extremely harmful" territory.

How does code like this pass any code review?

7

u/SyntheticDuckFlavour 2d ago

Disagree. Namespaces like these explicitly organises and communicates which component you are interacting with at any given moment. It also avoids clashes with class definitions and type names. Furthermore, APIs can to grow independently without the worrying about polluting other namespaces or introducing new conflicts with existing class definitions. Bit of a nothingburger, really. You can always just drop using namespace winrt::Windows::UI::Xaml::Interop within an implementation scope.

-1

u/Ateist 2d ago edited 2d ago

It doesn't help in the slightest if you can't read and understand the damn function because it keeps filling your mind's context window with irrelevant things - I keep losing the main point because of all that verbosity.

Reading the code is already the most difficult part of the job - and code like this makes it impossible.

If you really, really need to accommodate poorly built APIs like that

You can always just drop using namespace winrt::Windows::UI::Xaml::Interop within an implementation scope.

it shouldn't be optional - it should be mandatory.

and it's better to do

using Interop = winrt::Windows::UI::Xaml::Interop  

to improve readability without polluting the namespace with things you don't want.
It might also be better to do

using namespace winrt::Windows  

to reuse the common part.

2

u/mredding 1d ago

It doesn't help in the slightest if you can't read and understand the damn function because it keeps filling your mind's context window with irrelevant thing

If...

I look right past it. I don't give a damn what these things are because I don't have to know. Just symbolically, it's a thing - in a template parameter.

You need to train your brain to ignore the irrelevant.

and it's better to do [...] to improve readability without polluting the namespace

And that's what OP said, and is agreeable. I'd do it, and I do it even when writing example code on Reddit.

It might also be better to do

No, this is a contradiction of your previous point that avoiding namespace pollution is a virtue. It's better to alias the namespace.

1

u/Ateist 1d ago

I look right past it. I don't give a damn what these things are because I don't have to know.

I do too - but not when you have that many of them.

2

u/SyntheticDuckFlavour 1d ago

it shouldn't be optional - it should be mandatory.

Disagree. Implicit using namespace will create clashes. There is a reason why using namespace in header scope is considered bad practice and it should be only used in implementation scope.

1

u/Ateist 1d ago

When put it somewhere with restricted scope (or create a synonim namespace for it).

Namespaces aside from the last one and maybe second last one are harmful.

3

u/teerre 1d ago

It's hard to read because it's spells exactly where the code is coming from? How is that possible? Do you define "hard to read" by how many characters is has?

The hard to read version of this would be if it was imported from some header and you had no idea which

6

u/Ateist 1d ago

It's hard to read because the essential parts are buried under a ton of irrelevant staff.

Instead of having everything in single, clearly understandable portions it mixes in lots of information that is no way essential to the function in question.

The only portion in that line that play any part is:

Typename const&

0

u/teerre 1d ago

What? How is the namespace irrelevant? It's literally where the type resides

2

u/Ateist 1d ago

When you are writing note to your neghbour you don't start his address with

Milky_Way_Galaxy:Solar_System:Planet_Earth:United_States_Of_America:California:Hollywood

You just state "Apartment 12" so that it doesn't go to another neigbour next door.

1

u/teerre 1d ago

I mean, disregarding the fact that the whole mail system worldwide indeed requires full addresses, what do addresses in notes to neighbors have to with this?

1

u/EmbarkVatyx 1d ago

They are just saying the code in the post was illustrating a point which was hard to follow due to the verbosity of the type (notably the specific type is irrelevant to the broader point). If the type was named T instead it would be easier to see what’s going on. I had the same issue.

1

u/teerre 1d ago

That example is clearly copied from real code. A exemplified version is just below and uses no names paces

1

u/Ateist 15h ago

Because all the namespaces listed aside from the very last one are the worldwide address of that parameter.

u/NilacTheGrim 2h ago

I agree. Namespaces shouldn't be this super complicated taxonomy like in biology. It's really bad usage of namespaces.

0

u/sephirostoy 2d ago

East const doesn't help neither. 

9

u/lonkamikaze 2d ago

It actually does if you read types right to left. Reference to const TypeName instead of const (skip all the namespaces) TypeName reference.