Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.c++ > #83972 > unrolled thread

Virtual base constructor arguments and constructor delegation

Started byMarcel Mueller <news.5.maazl@spamgourmet.org>
First post2022-05-07 13:24 +0200
Last post2022-05-08 09:34 -0400
Articles 7 — 4 participants

Back to article view | Back to comp.lang.c++


Contents

  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

#83972 — Virtual base constructor arguments and constructor delegation

FromMarcel Mueller <news.5.maazl@spamgourmet.org>
Date2022-05-07 13:24 +0200
SubjectVirtual 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]


#83973

From"Alf P. Steinbach" <alf.p.steinbach@gmail.com>
Date2022-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]


#83982

FromMarcel Mueller <news.5.maazl@spamgourmet.org>
Date2022-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]


#83987

FromManfred <noname@add.invalid>
Date2022-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]


#83991

From"Alf P. Steinbach" <alf.p.steinbach@gmail.com>
Date2022-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]


#83998

FromManfred <noname@add.invalid>
Date2022-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]


#83983

FromSam <sam@email-scan.com>
Date2022-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