Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.c++ > #83972 > unrolled thread
| Started by | Marcel Mueller <news.5.maazl@spamgourmet.org> |
|---|---|
| First post | 2022-05-07 13:24 +0200 |
| Last post | 2022-05-08 09:34 -0400 |
| Articles | 7 — 4 participants |
Back to article view | Back to comp.lang.c++
Virtual base constructor arguments and constructor delegation Marcel Mueller <news.5.maazl@spamgourmet.org> - 2022-05-07 13:24 +0200
Re: Virtual base constructor arguments and constructor delegation "Alf P. Steinbach" <alf.p.steinbach@gmail.com> - 2022-05-07 14:25 +0200
Re: Virtual base constructor arguments and constructor delegation Marcel Mueller <news.5.maazl@spamgourmet.org> - 2022-05-08 11:45 +0200
Re: Virtual base constructor arguments and constructor delegation Manfred <noname@add.invalid> - 2022-05-08 18:56 +0200
Re: Virtual base constructor arguments and constructor delegation "Alf P. Steinbach" <alf.p.steinbach@gmail.com> - 2022-05-09 00:18 +0200
Re: Virtual base constructor arguments and constructor delegation Manfred <noname@add.invalid> - 2022-05-09 14:56 +0200
Re: Virtual base constructor arguments and constructor delegation Sam <sam@email-scan.com> - 2022-05-08 09:34 -0400
| From | Marcel Mueller <news.5.maazl@spamgourmet.org> |
|---|---|
| Date | 2022-05-07 13:24 +0200 |
| Subject | Virtual base constructor arguments and constructor delegation |
| Message-ID | <t55kt8$qn8s$1@gwaiyur.mb-net.net> |
Assuming the following class tree, the classic diamond but with all
classes without a standard constructor:
class Interpolation
{public:
Interpolation(int count);
// ...
}
class PolarInterpolation : public virtual Interpolation
{public:
PolarInterpolation(int count) : Interpolation(2*count) {}
// ...
}
class FileInterpolation : protected virtual Interpolation
{public:
FileInterpolation(const char* filename, int count)
: Interpolation(count)
{ // ...
}
}
class PolarFileInterpolation
: public FileInterpolation, protected PolarInterpolation
{public:
PolarFileInterpolation(const char* filename, int count)
: Interpolation(2*count)
, FileInterpolation(filename, 0) // unused parameter count
, PolarInterpolation(0) // unused parameter count
{}
}
The constructor of PolarFileInterpolation supplies count for all 3 base
constructors but this is pointless except for the first call to the
virtual base.
While most compiler likely eliminate the nonsense parameters at runtime
they look strange in the code. Furthermore it might not be that easy to
get nonsense values for e.g. reference types.
To come around this one could add an additional constructor overload to
the base classes that do not initialize the virtual base. But there are
some drawbacks:
- 1st it is impossible to define an "abstract" constructor that is
intended for sub classes only and only initializes the current class
without adding a default constructor to the virtual base class that will
never be called.
- 2nd the additional default constructor in the virtual base may
introduce errors when a derived class misses to invoke the "real"
constructor.
- 3rd the (maybe complex) constructor of the derived classes need to be
implemented twice in this case. Constructor forwarding does not work
together with different constructors of the virtual base, although one
of them will never be called.
class FileInterpolation : protected virtual Interpolation
{protected:
FileInterpolation(const char* filename); // init this class onlyy
public:
FileInterpolation(const char* filename, int count)
: Interpolation(count)
, FileInterpolation(filename) // does not work
{}
}
Is there a smarter solution?
(Standard is C++11 for now)
Marcel
[toc] | [next] | [standalone]
| From | "Alf P. Steinbach" <alf.p.steinbach@gmail.com> |
|---|---|
| Date | 2022-05-07 14:25 +0200 |
| Message-ID | <t55oga$uou$1@dont-email.me> |
| In reply to | #83972 |
On 7 May 2022 13:24, Marcel Mueller wrote:
> Assuming the following class tree, the classic diamond but with all
> classes without a standard constructor:
>
> class Interpolation
> {public:
> Interpolation(int count);
> // ...
> }
>
> class PolarInterpolation : public virtual Interpolation
> {public:
> PolarInterpolation(int count) : Interpolation(2*count) {}
> // ...
> }
>
> class FileInterpolation : protected virtual Interpolation
> {public:
> FileInterpolation(const char* filename, int count)
> : Interpolation(count)
> { // ...
> }
> }
>
> class PolarFileInterpolation
> : public FileInterpolation, protected PolarInterpolation
> {public:
> PolarFileInterpolation(const char* filename, int count)
> : Interpolation(2*count)
> , FileInterpolation(filename, 0) // unused parameter count
> , PolarInterpolation(0) // unused parameter count
> {}
> }
>
Strange use of `public` and `protected`, where from a client code of
view a `PolarFileInterpolation` is not an `Interpolation`. Huh.
> The constructor of PolarFileInterpolation supplies count for all 3 base
> constructors but this is pointless except for the first call to the
> virtual base.
> While most compiler likely eliminate the nonsense parameters at runtime
> they look strange in the code. Furthermore it might not be that easy to
> get nonsense values for e.g. reference types.
>
> To come around this one could add an additional constructor overload to
> the base classes that do not initialize the virtual base. But there are
> some drawbacks:
> - 1st it is impossible to define an "abstract" constructor that is
> intended for sub classes only and only initializes the current class
> without adding a default constructor to the virtual base class that will
> never be called.
While the details you sketch may be impossible the general idea is not
impossible.
> - 2nd the additional default constructor in the virtual base may
> introduce errors when a derived class misses to invoke the "real"
> constructor.
> - 3rd the (maybe complex) constructor of the derived classes need to be
> implemented twice in this case. Constructor forwarding does not work
> together with different constructors of the virtual base, although one
> of them will never be called.
>
> class FileInterpolation : protected virtual Interpolation
> {protected:
> FileInterpolation(const char* filename); // init this class onlyy
> public:
> FileInterpolation(const char* filename, int count)
> : Interpolation(count)
> , FileInterpolation(filename) // does not work
> {}
> }
>
> Is there a smarter solution?
>
> (Standard is C++11 for now)
Just the idea you sketched of dummy initialization, but with slightly
different details:
#include <tuple>
using std::ignore;
using C_str = const char*;
class Interpolation
{
protected:
struct Dummy_init {};
Interpolation( Dummy_init ) { throw 666; }
public:
Interpolation( const int count ) { ignore = count; }
};
class Abstract_polar_interpolation:
public virtual Interpolation
{
protected:
Abstract_polar_interpolation():
Interpolation( Dummy_init() )
{}
};
class Polar_interpolation final:
public virtual Abstract_polar_interpolation
{
public:
Polar_interpolation( const int count ):
Interpolation( 2*count ),
Abstract_polar_interpolation()
{}
};
class Abstract_file_interpolation:
public virtual Interpolation
{
protected:
Abstract_file_interpolation( const C_str filename ):
Interpolation( Dummy_init() ) // Isn't actually called.
{ ignore = filename; }
};
class File_interpolation final:
public Abstract_file_interpolation
{
public:
File_interpolation( const C_str filename, const int count ):
Interpolation(count),
Abstract_file_interpolation( filename )
{ ignore = filename; }
};
class Polar_file_interpolation final:
public Abstract_file_interpolation,
public Abstract_polar_interpolation
{
public:
Polar_file_interpolation( const C_str filename, const int count ):
Interpolation( 2*count ),
Abstract_file_interpolation( filename ),
Abstract_polar_interpolation()
{}
};
auto main() -> int
{
auto pi = Polar_interpolation( 123 );
auto fi = File_interpolation( "balthazar", 99 );
auto pfi = Polar_file_interpolation( "pfi", -1 );
(void) pi; (void) fi; (void) pfi;
}
Cheers,
- Alf
[toc] | [prev] | [next] | [standalone]
| From | Marcel Mueller <news.5.maazl@spamgourmet.org> |
|---|---|
| Date | 2022-05-08 11:45 +0200 |
| Message-ID | <t583g2$12vfl$1@gwaiyur.mb-net.net> |
| In reply to | #83973 |
Am 07.05.22 um 14:25 schrieb Alf P. Steinbach:
> Strange use of `public` and `protected`, where from a client code of
> view a `PolarFileInterpolation` is not an `Interpolation`. Huh.
Strictly speaking PolarFileInterpolation (and descendants) only uses
Interpolation. So inheritance does not exactly hit the nail on the head.
But it simplifies interaction because the derived class can easily add
some missing parts by implementation of abstract functions rather than
dealing with functors as constructor arguments to Interpolation.
Additionally this would raise the problem who manages the storage for
Interpolation. It either requires dynamic allocation or I still have the
same problem with the Interpolation instance.
A clean solution probably needs a template argument for the
Interpolation to use. But I hate the ugly error messages (w/o C++20
concepts). And it also increases compilation time since separation into
compilation units does not really work with templates.
> While the details you sketch may be impossible the general idea is not
> impossible.
>
> Just the idea you sketched of dummy initialization, but with slightly
> different details:
>
>
> #include <tuple>
> using std::ignore;
>
> using C_str = const char*;
>
> class Interpolation
> {
> protected:
> struct Dummy_init {};
> Interpolation( Dummy_init ) { throw 666; }
Hmm, this at least prevents accidental default initialization.
I think the additional classes of your example are not really required
for this to work. Although I dislike functions that should never be called.
I did not need virtual inheritance very often and probably never with a
non default constructor so far. But since a virtual base can only be
initialized by the deepest class I expected some syntax to declare
"incomplete" constructors that can only be used for derived classes.
Something like
class PolarInterpolation : public virtual Interpolation
{protected:
PolarInterpolation() = 0 {}
Marcel
[toc] | [prev] | [next] | [standalone]
| From | Manfred <noname@add.invalid> |
|---|---|
| Date | 2022-05-08 18:56 +0200 |
| Message-ID | <t58soq$eas$1@gioia.aioe.org> |
| In reply to | #83982 |
On 5/8/2022 11:45 AM, Marcel Mueller wrote:
> Am 07.05.22 um 14:25 schrieb Alf P. Steinbach:
<snip>
>> While the details you sketch may be impossible the general idea is not
>> impossible.
>>
>> Just the idea you sketched of dummy initialization, but with slightly
>> different details:
>>
>>
>> #include <tuple>
>> using std::ignore;
>>
>> using C_str = const char*;
>>
>> class Interpolation
>> {
>> protected:
>> struct Dummy_init {};
>> Interpolation( Dummy_init ) { throw 666; }
>
> Hmm, this at least prevents accidental default initialization.
> I think the additional classes of your example are not really required
> for this to work. Although I dislike functions that should never be called.
>
>
> I did not need virtual inheritance very often and probably never with a
> non default constructor so far. But since a virtual base can only be
> initialized by the deepest class I expected some syntax to declare
> "incomplete" constructors that can only be used for derived classes.
> Something like
>
> class PolarInterpolation : public virtual Interpolation
> {protected:
> PolarInterpolation() = 0 {}
>
What's wrong with:
class Interpolation
{
protected:
// struct Dummy_init {};
Interpolation( ); // decl only
public:
Interpolation( const int count ) { ignore = count; }
};
and get rid of all
// Interpolation( Dummy_init() )
and replace with:
: Interpolation( )
This wouldn't work if you need to have a Interpolation( ) constructor
that needs to be legitimately called somewhere else, however the obvious
idea is to use a constructor that is declared but not defined.
>
> Marcel
[toc] | [prev] | [next] | [standalone]
| From | "Alf P. Steinbach" <alf.p.steinbach@gmail.com> |
|---|---|
| Date | 2022-05-09 00:18 +0200 |
| Message-ID | <t59fkl$r4h$1@dont-email.me> |
| In reply to | #83987 |
On 8 May 2022 18:56, Manfred wrote:
> On 5/8/2022 11:45 AM, Marcel Mueller wrote:
>> Am 07.05.22 um 14:25 schrieb Alf P. Steinbach:
>
> <snip>
>
>>> While the details you sketch may be impossible the general idea is
>>> not impossible.
>>>
>>> Just the idea you sketched of dummy initialization, but with slightly
>>> different details:
>>>
>>>
>>> #include <tuple>
>>> using std::ignore;
>>>
>>> using C_str = const char*;
>>>
>>> class Interpolation
>>> {
>>> protected:
>>> struct Dummy_init {};
>>> Interpolation( Dummy_init ) { throw 666; }
>>
>> Hmm, this at least prevents accidental default initialization.
>> I think the additional classes of your example are not really required
>> for this to work. Although I dislike functions that should never be
>> called.
>>
>>
>> I did not need virtual inheritance very often and probably never with
>> a non default constructor so far. But since a virtual base can only be
>> initialized by the deepest class I expected some syntax to declare
>> "incomplete" constructors that can only be used for derived classes.
>> Something like
>>
>> class PolarInterpolation : public virtual Interpolation
>> {protected:
>> PolarInterpolation() = 0 {}
>>
>
> What's wrong with:
>
> class Interpolation
> {
> protected:
> // struct Dummy_init {};
> Interpolation( ); // decl only
>
> public:
> Interpolation( const int count ) { ignore = count; }
> };
>
> and get rid of all
> // Interpolation( Dummy_init() )
> and replace with:
> : Interpolation( )
>
>
> This wouldn't work if you need to have a Interpolation( ) constructor
> that needs to be legitimately called somewhere else, however the obvious
> idea is to use a constructor that is declared but not defined.
In a correct program the dummy constructor is never called at run-time.
Still, unless the compiler is smart, it may/will emit a call
instruction, guarded by some boolean.
Then the linker needs a definition.
And with a definition it's easy to call that dummy default constructor
inadvertently.
That's a good reason to make all such calls stand out, via a dummy
parameter.
- Alf
[toc] | [prev] | [next] | [standalone]
| From | Manfred <noname@add.invalid> |
|---|---|
| Date | 2022-05-09 14:56 +0200 |
| Message-ID | <t5b32m$ci3$1@gioia.aioe.org> |
| In reply to | #83991 |
On 5/9/2022 12:18 AM, Alf P. Steinbach wrote:
> On 8 May 2022 18:56, Manfred wrote:
>> On 5/8/2022 11:45 AM, Marcel Mueller wrote:
>>> Am 07.05.22 um 14:25 schrieb Alf P. Steinbach:
>>
>> <snip>
>>
>>>> While the details you sketch may be impossible the general idea is
>>>> not impossible.
>>>>
>>>> Just the idea you sketched of dummy initialization, but with
>>>> slightly different details:
>>>>
>>>>
>>>> #include <tuple>
>>>> using std::ignore;
>>>>
>>>> using C_str = const char*;
>>>>
>>>> class Interpolation
>>>> {
>>>> protected:
>>>> struct Dummy_init {};
>>>> Interpolation( Dummy_init ) { throw 666; }
>>>
>>> Hmm, this at least prevents accidental default initialization.
>>> I think the additional classes of your example are not really
>>> required for this to work. Although I dislike functions that should
>>> never be called.
>>>
>>>
>>> I did not need virtual inheritance very often and probably never with
>>> a non default constructor so far. But since a virtual base can only
>>> be initialized by the deepest class I expected some syntax to declare
>>> "incomplete" constructors that can only be used for derived classes.
>>> Something like
>>>
>>> class PolarInterpolation : public virtual Interpolation
>>> {protected:
>>> PolarInterpolation() = 0 {}
>>>
>>
>> What's wrong with:
>>
>> class Interpolation
>> {
>> protected:
>> // struct Dummy_init {};
>> Interpolation( ); // decl only
>>
>> public:
>> Interpolation( const int count ) { ignore = count; }
>> };
>>
>> and get rid of all
>> // Interpolation( Dummy_init() )
>> and replace with:
>> : Interpolation( )
>>
>>
>> This wouldn't work if you need to have a Interpolation( ) constructor
>> that needs to be legitimately called somewhere else, however the
>> obvious idea is to use a constructor that is declared but not defined.
>
> In a correct program the dummy constructor is never called at run-time.
>
> Still, unless the compiler is smart, it may/will emit a call
> instruction, guarded by some boolean.
I take your word for it, but to me it seems that a compiler that does
that is a poor one, it's not that compilers that don't are smart.
After all, all of the information that is needed to not make the call is
clearly available at compile time.
>
> Then the linker needs a definition.
>
> And with a definition it's easy to call that dummy default constructor
> inadvertently.
I'm sure you remember the time when we had no "= delete", and we used to
declare private copy constructors with no definition to solve this kind
of problem.
>
> That's a good reason to make all such calls stand out, via a dummy
> parameter.
>
>
> - Alf
>
>
[toc] | [prev] | [next] | [standalone]
| From | Sam <sam@email-scan.com> |
|---|---|
| Date | 2022-05-08 09:34 -0400 |
| Message-ID | <cone.1652016852.864283.21572.1004@monster.email-scan.com> |
| In reply to | #83972 |
[Multipart message — attachments visible in raw view] — view raw
Marcel Mueller writes:
> Assuming the following class tree, the classic diamond but with all classes
> without a standard constructor:
>
> class Interpolation
> {public:
> Interpolation(int count);
> // ...
> }
>
> class PolarInterpolation : public virtual Interpolation
> {public:
> PolarInterpolation(int count) : Interpolation(2*count) {}
> // ...
> }
>
> class FileInterpolation : protected virtual Interpolation
> {public:
> FileInterpolation(const char* filename, int count)
> : Interpolation(count)
> { // ...
> }
> }
>
> class PolarFileInterpolation
> : public FileInterpolation, protected PolarInterpolation
> {public:
> PolarFileInterpolation(const char* filename, int count)
> : Interpolation(2*count)
> , FileInterpolation(filename, 0) // unused parameter count
> , PolarInterpolation(0) // unused parameter count
> {}
> }
>
> The constructor of PolarFileInterpolation supplies count for all 3 base
> constructors but this is pointless except for the first call to the virtual
> base.
> While most compiler likely eliminate the nonsense parameters at runtime they
> look strange in the code.
The only thing that would look strange is that the "not-in-charge-of-virtual-
construction" constructors will have the full stack frame, for all of its
parameters, and just never reference what's not used. I am not 100% sure
that the compiler can use the One Definition Rule to rely on all the
constructors' definitions to be inline in order to completely eliminate the
unused portion of the stack frame, at the call and the called site. After
all, the same exact classes can be defined individually, in other
translation units, and as long as all the definitions are identically, I
believe this does not violate the ODR, and the compiler will not have all
the info, when compiling those, to optimize it away.
With that established: this wouldn't be any different than any other regular
function that never uses one of its parameters. This can happen in any
number of reasonable situations, so this is not really some novelty, here.
That doesn't look strange to me.
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.c++
csiph-web