Friday, October 7, 2022

Digest for comp.lang.c++@googlegroups.com - 11 updates in 5 topics

Tim Rentsch <tr.17687@z991.linuxsc.com>: Oct 07 04:00PM -0700

> With very few exceptions.
> The only static polymorphism I wholeheartedly approve is
> polymorphism of arithmetic operators for built-in types.
 
Not that I disagree necessarily, but can you say what it is about
function overloading you don't like? (For the time being let's
ignore the larger topic of polymorphism with templates.)
Andrey Tarasevich <andreytarasevich@hotmail.com>: Oct 07 12:41PM -0700


> Visual Studio 2022 (v143) says "no", "'f': is not a member of 'Foo<std::basic_string_view<char,std::char_traits<char>>,void>", 17
> at line (*)
 
> Both compiled with C++17.
 
Your program is invalid (ill-formed). However, no diagnostic is required.
 
An issue of the same general nature can be demonstrated by a more simple
example
 
class C;
 
template <typename T = int> void foo() {
C c; // Incomplete type error? Or not?
}
 
int main() {
foo();
}
 
class C {};
 
Note that the above code is also quietly accepted by GCC, but rejected
by MSVC and Clang.
 
Briefly and informally, the rule it violates says the following: if the
interpretation of your template changes depending on its point of
instantiation, the program is ill-formed. In a more focused and formal
form: if a reference to an non-dependent name from an imaginary
instantiation that immediately follows the definition is invalid, the
program is ill-formed.
 
A complete set of formal requirements can be found in [temp.res]:
 
https://timsong-cpp.github.io/cppwp/n4659/temp.res#8.3
https://timsong-cpp.github.io/cppwp/n4659/temp.res#8.4
https://timsong-cpp.github.io/cppwp/n4659/temp.res#temp.point-8
 
The first two links deal with non-dependent names, while the third one
applies to dependent names. But the general idea is the same: if the
meaning of a template specialization depends on its point of
instantiation, the program is ill-formed. If the meaning of a template
specialization changes as you move it up and down in the translation
unit, the program is ill-formed. Yet, no diagnostic is required.
 
This is basically it.
 
But there's another detail at play here. The freedom provided by "no
diagnostic is required" supplies the implementations with quite a bit of
leeway in choosing the point of instantiation. The obvious next logical
step is the resolution of DR#993
 
https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#993
 
It allows implementations to stop worrying about choosing the proper
point of instantiation completely. It allows them to simply push all
instantiations to the very end of the translation unit and be done with it.
 
This is an opportunity GCC immediately took advantage of. GCC
instantiates templates at the very end of the TU and, expectedly,
interprets their content from that vantage point. Which is why GCC
reports errors neither in your example nor in mine. Meanwhile, Clang and
MSVC still stick to the "early instantiation" approach, which allows
them to see the problems in our examples.
 
--
Best regards,
Andrey
David Brown <david.brown@hesbynett.no>: Oct 07 09:07AM +0200

On 06/10/2022 22:07, Lynn McGuire wrote:
> typedef unsigned long long ulongint;    /* system-dependent */
> #define  fstring  fem::str
 
> Lynn
 
An alternative if you want to keep C90 support (well, C90 with "long
long" support) would be to include <limits.h> and add some compile-time
checks on the sizes. The point is to be sure that the sizes are what
you expect them to be.
 
You will also want to change the "logical1" and "integer1" typedefs. It
is madness to use a plain "char" for anything other than an ASCII
character. You want "signed char" for "integer1", and I would have
thought "unsigned char" is the appropriate choice for "logical1".
Lynn McGuire <lynnmcguire5@gmail.com>: Oct 07 02:23PM -0500

On 10/7/2022 2:07 AM, David Brown wrote:
> is madness to use a plain "char" for anything other than an ASCII
> character.  You want "signed char" for "integer1", and I would have
> thought "unsigned char" is the appropriate choice for "logical1".
 
I don't use those types in my code so I don't care. Those types are
just in the standard f2c.h declaration.
 
Thanks,
Lynn
Jivanmukta <jivanmukta@poczta.onet.pl>: Oct 07 07:34AM +0200

W dniu 6.10.2022 o 08:55, Juha Nieminen pisze:
> code until you can't remove anything without making the problem
> disappear. Anything that doesn't affect the problem, remove, until
> you have the minimal amount of code that still exhibits the problem.
 
I will try do this but it won't be easy task - my program has nearly 10
KLOC.
Jivanmukta <jivanmukta@poczta.onet.pl>: Oct 07 03:20PM +0200

W dniu 7.10.2022 o 07:34, Jivanmukta pisze:
> I will try do this but it won't be easy task - my program has nearly 10
> KLOC.
 
Moreover, my program consists of many highly-coupled files.
Juha Nieminen <nospam@thanks.invalid>: Oct 07 09:47AM

I have encountered this small dilemma several times. It's a question of
const correctness and proper OO design in C++.
 
Should a virtual function in a base class be const or not? This especially
in cases where it's very possible that a derived implementation wants
to change its own state (and thus would want the function to not be
const).
 
The answer seems simple enough: Obviously it's better to not make it
const, to allow for this possibility.
 
Problem is, when it's not const, now it can't be called using a const
instance, or const reference or pointer to one. There may be situations
where the only thing you have is a const version of the object, and
there's no way around that.
 
(Ok, technically speaking you *can* call it, by using const_cast. But
that's a really ugly solution, bad design, and in some cases might
even potentially break something. (Const code might assume the object
doesn't change.))
 
"Well, in that case offer *both* versions of the virtual function in
the base class, duh! Problem solved!"
 
Except that sometimes, in some situations, there may not be any
reasonable way to implement a const version of a function, because
it *has to* change the object in order to function correctly.
Sometimes a const version just doesn't make any sense. (For example,
a const version of std::vector::push_back() wouldn't make much
sense. What would it be supposed to do? Nothing? That would make
it really odd design, and would have the potential to break
something.)
 
This isn't even a question of "const correctness". It's a question
of getting a compiler error if all you have is a const reference
(and no way around that) and the virtual function happens to be
non-const (even though the implementation doesn't actually change
anything).
David Brown <david.brown@hesbynett.no>: Oct 07 12:50PM +0200

On 07/10/2022 11:47, Juha Nieminen wrote:
> instance, or const reference or pointer to one. There may be situations
> where the only thing you have is a const version of the object, and
> there's no way around that.
 
Shouldn't it be a matter of deciding whether the function logically
changes the object or not? If it clearly changes the object, it must be
non-const. But if it leaves the object with the same publicly visible
state, make it const. Any later bits that might need changing should be
mutable.
 
A virtual function is an interface, not implementation detail. It
doesn't make sense to me to have a virtual function that is logically
const in a base class, but logically non-const in a derived class.
Paavo Helde <eesnimi@osa.pri.ee>: Oct 07 02:00PM +0300

07.10.2022 12:47 Juha Nieminen kirjutas:
> in cases where it's very possible that a derived implementation wants
> to change its own state (and thus would want the function to not be
> const).
 
IMO, the 'const' should be applied logically. There is a virtual
function which does something. Is this something that conceptually
should not modify the object state? If yes, then make the virtual
function const.
 
If a derived class overrides this function, it should conceptually still
do something which does not modify the object. If it conceptually does,
then it's possible you have bigger problems with the design, starting
with the function name.
 
If the derived class modifies its state only technically, e.g. by
caching something, we have means to express this in C++ cleanly - that's
the 'mutable' keyword.
 
When following these rules, now if you have a const reference to the
object and need to call a non-const virtual function on it, this means
you want to call a function on a const object which might (again,
conceptually) modify the object state. That's a design smell, and this
is the exact reason why the language has the const qualifier in the
first place - to catch such things at compile time so the design could
be fixed.
 
In reality the things won't work out so nicely. In some cases the base
class virtual interface is at so abstract level it is impossible to say
if the functions might conceptually modify the object or not. According
to the above guidelines these virtual functions should be non-const,
which in turn means that this class cannot be really used via const
references. In my code I have got several "non-const" classes which are
basically always accessed only over non-const references. Yes, this
looses the benefits of 'const', but at least it is honest about that and
does not hide it behind a wall of const_cast-s.
 
Other scenario is e.g. with virtual functions which take callbacks or
functors as arguments. Some callbacks might want to modify the object,
some not. In that case one should provide two different virtual
functions, one const and the other non-const.
 
If there is some override which does not make sense and which should not
be called, then it's easy for me. I just put a debug assert failure and
a runtime exception there.
 
hth
Paavo
Richard Damon <Richard@Damon-Family.org>: Oct 07 07:29AM -0400

On 10/7/22 5:47 AM, Juha Nieminen wrote:
> (and no way around that) and the virtual function happens to be
> non-const (even though the implementation doesn't actually change
> anything).
 
The answer is that if the state isn't part of the visible state, make
the state mutable, which means it CAN be changed in const objects and
not giv problems.
 
If the state is part of the visible state, then you shouldn't really do
that, as const functions shouldn't visible change the state, but it says
the overriding doesn't really apply. A caller thining they are doing a
constant operation on an object shouldn't find the object having changed.
 
You have a design issue is you have an operation that inherently might
change the object but also needs to be implemented on const objects.
 
THAT is your problem.
"Öö Tiib" <ootiib@hot.ee>: Oct 07 05:55AM -0700

On Friday, 7 October 2022 at 12:47:29 UTC+3, Juha Nieminen wrote:
> in cases where it's very possible that a derived implementation wants
> to change its own state (and thus would want the function to not be
> const).
 
If it is not changing base class state only because the base class is kind
of dummy/refusing but otherwise it is logical to change state then of
course do not make method const. If it changes derived state only
because of some internal efficiency or safety (caching, reporting, lazy
loading) but otherwise it is logical not to change state then make
member const but that state mutable.
 
> instance, or const reference or pointer to one. There may be situations
> where the only thing you have is a const version of the object, and
> there's no way around that.
 
If it logically mutates state of object then that is good that it can not
be called on const object.
 
> that's a really ugly solution, bad design, and in some cases might
> even potentially break something. (Const code might assume the object
> doesn't change.))
 
Yes. That can break something.
 
 
> Except that sometimes, in some situations, there may not be any
> reasonable way to implement a const version of a function, because
> it *has to* change the object in order to function correctly.
 
So you want the call to compile on const object despite it can't be
done on the const version? Then the const version can throw or refuse
in some other manner.
 
> sense. What would it be supposed to do? Nothing? That would make
> it really odd design, and would have the potential to break
> something.)
 
Then it is perfect that attempt to call push_back() on const object
gives compiling error.
 
> (and no way around that) and the virtual function happens to be
> non-const (even though the implementation doesn't actually change
> anything).
 
We can't have some kind of pre-decided silver bullet here. On
some cases we make virtual function non-const on other cases
const and on third cases provide both overloads. Logic is more or
less same as without dynamic polymorphism.
As always there can be some kind of grey area where it is not
crisp clear what to do but such example needs to be looked at and
decided concretely and individually, general rules are hard to
establish.
You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page.
To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com.

No comments: