0

For some complicated reasons I want to create default constructor (alongside my normal constructors) that always throws. I want it to be there, but I also want it never to be called. It is pretty obvious that during runtime I can check for that thrown exception and for example terminate program when I catch it, but the ideal solution would be to have it checked during compilation.

So my question is: can I statically assert somehow that a function will never be called? I've looked at functions in <type_traits> but I don't see anything there that would help me. Is there some c++ dark magic that I could use to achieve my goal?

I don't have a code example, because what would even be in there?

PS. Yes. I am sure that I want to have a function and disallow everybody of calling it. As I stated previously reasons for that are complicated and irrelevant to my question.

EDIT. I can't delete this constructor or make it private. It has to be accessible for deriving classes, but they shouldn't call it. I have a case of virtual inheritance and want to "allow" calling this constructor by directly virtually derived classes (they won't call it, but c++ still requires it to be there), but no in any other classes deeper in inheritance chain.

EDIT 2. As requested I give a simplified example of my code.

#include <stdexcept>

class Base {
protected:
  Base() { throw std::logic_error{"Can't be called"}; }
  Base(int); // proper constructor
  
private:
  // some members initialized by Base(int)
};

class Left: virtual public Base {
protected:
  Left(int) {}
  // ^ initialize members of Left, but does not call Base()!
  // Though it seems that it implicitly does, Base() is never actually called.
};

class Right: virtual public Base {
protected:
  Right(int) {} // The same as in Left
};

class Bottom: public Left, public Right {
public:
  Bottom(int b, int l, int r): Base{b}, Left{l}, Right{r} {}
  // ^ Explicitly calling constructors of Base, Left, Right.
  // If I forget about calling Base(int) it silently passes
  // and throws during runtime. Can I prevent this?
};

EDIT 3. Added body to Left's and Right's constructors, so that they implicitly "call" Base().

Aleksander Krauze
  • 3,115
  • 7
  • 18
  • 2
    Why not explicitly `delete` the function? Should work in C++11 and later. – Stephen Newell Jun 05 '22 at 22:21
  • 1
    You could mark the routine as `[[deprecated]]`, or you could make it `private:`, or you could mark it `=delete`. The routine's body could have `throw std::logic_error("never supposed to be called");`. – Eljay Jun 05 '22 at 22:22
  • 1
    `=delete`ing the function is the way to go. – HolyBlackCat Jun 05 '22 at 22:30
  • 1
    *c++ still requires it to be there* That doesn't sound right. No class is required to have a default constructor (unless it is used to default initialize object of that class). Do you actually want to use [member initializer lists](https://en.cppreference.com/w/cpp/language/constructor) in your derived classes to call a different base constructor? – Yksisarvinen Jun 05 '22 at 22:30
  • 3
    Code is worth 1000 words – Taekahn Jun 05 '22 at 22:31
  • 2
    *"It has to be accessible for deriving classes, but they shouldn't call it."* -- this is contradictory. Your "directly virtually derived classes" *would* be calling this constructor as far as (simple) static analysis goes. Perhaps you could look into how to prevent the "directly virtually derived classes" from being directly instantiated? – JaMiT Jun 05 '22 at 22:31
  • 2
    *"I don't have a code example, because what would even be in there?"* -- at a guess: your class structure, showing why the default constructor needs to exist. I'm guessing three classes should do it -- the base class you're talking about, a class with a `virtual` base, and a class derived from the second. – JaMiT Jun 05 '22 at 22:33
  • 1
    You have presented nothing in your code that cannot be solved by making it private: [goldbolt](https://godbolt.org/z/K35W1jG3j) – Taekahn Jun 06 '22 at 14:46
  • @Taekahn You cannot make it private, because Left's and Right's constructors need to "call" Base constructor. Remove semicolon after Left() and replace it with curly braces, and see for yourself. – Aleksander Krauze Jun 06 '22 at 14:51
  • 1
    @AleksanderKrauze If you change the fact that you're not calling the default constructor, to call the default constructor, it will, in fact, call the default constructor. But the code presented isn't doing that. As i said _you have presented nothing in your code_ that cannot be solved by making it private. The whole point of providing the code was to provide an example of the problem you're trying to solve. If you want to provide a better example, i'll be more than happy to take another look – Taekahn Jun 06 '22 at 14:54
  • https://godbolt.org/z/3jjehvfn3 If you want the call to be something other than what i marked as `a` you can always pass it along. – Taekahn Jun 06 '22 at 15:01
  • After doing a little search, i would argue this is, in the end, a duplicate of this. https://stackoverflow.com/questions/10534228/order-of-constructor-call-in-virtual-inheritance – Taekahn Jun 06 '22 at 15:13
  • @Taekahn Firstly, thank you for your answers. I am aware of the order of constructor call in virtual inheritance. That is why I am trying to do what I described in my question. I find this bizarre that c++ requires that I "call" a constructor and then it doesn't call it. I thought about doing what you suggested [here](https://godbolt.org/z/3jjehvfn3), but I argue that it is even more confusing that what I am trying to do. Essentially I am trying to chose the smallest evil. – Aleksander Krauze Jun 06 '22 at 15:42
  • It is confusing, that is probably why people tend to avoid multiple virtual inheritance. If that suggestion is a no-go, i would suggest declaring the constructor as protected, but never actually defining it in your `.cpp` file (or anywhere) https://godbolt.org/z/68cad5xj9 it won't get you a compiler error since you never actually call it, but it will produce an error if any code actually tries to call it. You _could_ try some template "dark magic" as you put it, and it would probably work, but i would suggest it would be more complicated and confusing than anything presented so far. – Taekahn Jun 06 '22 at 16:05
  • Do you ever want to create instances of `Base`, `Left` or `Right`? or only ever inherit from them? – Elliott Jun 06 '22 at 16:25
  • @Taekahn I believe you are wrong. I can declare constructor and don't define it and it will work inside one compilation unit, but when I try to link it with another object file, linker (at least `ld` on linux) will give me an error saying that it has an undefined reference to `Base::Base()`. – Aleksander Krauze Jun 06 '22 at 16:28
  • 1
    @AleksanderKrauze interesting. Sorry i couldn’t be of anymore help. If something else comes to mind I’ll test it out. – Taekahn Jun 06 '22 at 16:30
  • @Elliott Only inherit. But I don't want to make them abstract classes. – Aleksander Krauze Jun 06 '22 at 16:30
  • @AleksanderKrauze, May I ask why? If it's something like [this](https://godbolt.org/z/E5Y31doTK) then I doubt the compiler is dumb enough to set up any pointers to vtables.... – Elliott Jun 06 '22 at 16:34
  • @Elliott I am worried that compiler will add this to vtable. Are you sure it won't do that? Also this is a simple example, but in my real use case I want Base, Left, and Right to have a bunch of variables and public functions. I read in [cpp core guidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md) that abstract classes shouldn't have any member variables and they seem pretty confident about this. – Aleksander Krauze Jun 06 '22 at 16:45

2 Answers2

1

As you've stated in your comments that you never want to instatiate Base, Left or Right object, then you should make them abstract, even by some empty method:

class Base {
private:
  // ...

  virtual void DefineIfNonAbstract() = 0;
};

class Bottom: public Left, public Right {

  void DefineIfNonAbstract() final {};

  // ...
};

Trust your compiler. When it sees that DefineIfNonAbstract is private and none of its parents implemented it, it's not going to put it into a vtable.

You're Bottom class is already 16 bytes in your example for both gcc and clang (likely a pointer for each virtual inheritance). Adding the abstract method doesn't change that.


In the comments you expressed concern that this might not be safe, and sent me a link to CppCoreGuidelines:

I.25: Prefer empty abstract classes as interfaces to class hierarchies

Reason

Abstract classes that are empty (have no non-static member data) are more likely to be stable than base classes with state.

They're referring to design choices here, not whether it causes undefined behaviour or something. In our case we're actually enforcing your design, not changing it.

The whole thing likely needs a serious rework in design. Inheritance in general is rarely a good choice - virtual inheritance even rarer.

Elliott
  • 2,603
  • 2
  • 18
  • 35
  • If `DefineIfNotAbstract` won't be put into vtable, then great! This seems to be the strongest enforcement of correctness. When it comes to inheritance I believe that in my use case this is still the best solution. I want to have a bunch of types that share common variables and function (Base in my example) and some subset of those should share another set of variables and functions (Left and Right in my example). Inheritance allows me to define those only once and keep my code DRY. – Aleksander Krauze Jun 06 '22 at 17:39
  • @AleksanderKrauze, fair enough. I can't know without being involved in your real-world code. – Elliott Jun 06 '22 at 17:44
  • 1
    @AleksanderKrauze, as for the vtable, you have to understand that it's the **_call_** to an abstract method that creates the slowdown - which in our case is definitively _never_. The only possible question is whether the compiler keeps a singular element in the vtable for the method (there's only ever one vtable for the entire course of the program). So _maybe_, if the compiler doesn't realise the obvious, then maybe you'll use an extra word of your read-only data segment. – Elliott Jun 06 '22 at 18:00
  • Of course! I didn't think about how may vtables will exist. Thank you for pointing that out for me. – Aleksander Krauze Jun 06 '22 at 18:13
0

If your link time optimization is up to the challenge, you might be able to have the problematic function call a never-defined function.

#include "Base.h"

// An anonymous namespace (or a static function) might be caught as missing
// while compiling this translation unit, so use a suggestive namespace name.
namespace DoNotDefine {
    // Declare a function without a definition.
    // The name is intended to make the error message easier to digest.
    void DisallowedConstruction();
} // namespace DoNotDefine

Base::Base()
{
    DisallowedConstruction();
}

In theory – I am not claiming that any particular linker is up to the task – the linker could eliminate unused function definitions before checking for missing definitions. If nothing actually calls Base::Base() then such a linker would eliminate its definition before complaining that DisallowedConstruction() has no definition. After eliminating Base::Base(), there is no longer a call to DisallowedConstruction() so no problem.

If something did actually call Base::Base() then linking would fail because of there is no definition for DisallowedConstruction().

Again, I am not claiming that this will actually work with your compiler chain, only that it could work in theory.


For lesser compilers, I would suggest defining a pure virtual function in Base. Keep this pure virtual until you hit a class that uses non-default construction for Base. That ensures that no one instantiates a default Base. A bit awkward, but effective.

However, this has the drawback that the compiler cannot enforce this convention. This convention could be innocently broken. For example, someone might change a class from using non-default construction of Base to default construction and forget to remove the definition of the special function.

JaMiT
  • 14,422
  • 4
  • 15
  • 31
  • Thank you. This is an interesting idea. I will try to test it. Unfortunately I am writing a library, so I cannot make any assumptions about compile chain. – Aleksander Krauze Jun 05 '22 at 22:53
  • Thank you for another suggestion. I've already thought about making Base, Left and Right classes abstract, but I've come to a conclusion that it is even worse for my use case. – Aleksander Krauze Jun 05 '22 at 22:58