9

Suppose I'm writing Derived and have to inherit from Base, which I don't control and has two separate constructors and a deleted copy and move constructors:

struct Base {
    Base(int i);
    Base(const char *sz);
    Base(const Base&) = delete;
    Base(const Base&&) = delete;
};

struct Derived {
    Derived(bool init_with_string);
};

Now, depending on the value of another_param I have to initialize my base class using either a constructor or the other; if C++ was a bit less strict it would be something like:

Derived::Derived(bool init_with_string) {
    if(init_with_string) {
        Base::Base("forty-two");
    } else {
        Base::Base(42);
    }
}

(this would also be useful for all the cases where it's cumbersome to calculate values to pass to base class constructors/fields initializers in straight expressions, but I'm digressing)

Unfortunately, even if I don't see particular codegen or object-model obstacles to this kind of thing, this isn't valid C++, and I cannot think of easy workaround.

Is there some way around this that I'm not aware of?

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • 4
    Have your derived class take both an int and const char * and call the appropriate base class constructor in the initialization list of the derived class. – Anon Mail Apr 28 '17 at 13:13
  • 1
    Would [such blasphemy](https://wandbox.org/permlink/tcrpEqRdhpfAAW1a) work? (I'd rather use Anon Mail's solution) – Marco A. Apr 28 '17 at 13:13
  • @MarcoA.: unfortunately `Base` is not copyable; also, that site hung my browser. – Matteo Italia Apr 28 '17 at 13:20
  • @SanderDeDycker: as M.M said, that just moves around the problem. – Matteo Italia Apr 28 '17 at 13:21
  • @MatteoItalia [it should work regardless](https://wandbox.org/permlink/UVQ1oTzhmedBuEaB) if it is move-able – Marco A. Apr 28 '17 at 13:22
  • @MarcoA.: unfortunately it's not. – Matteo Italia Apr 28 '17 at 13:23
  • 1
    @M.M - any class containing `std::mutex` must be non-movable (in fact, completely non-copyable). – Toby Speight Apr 28 '17 at 13:31
  • @M.M: besides, that's outside of my control anyway. But again, we are digressing, there's no real reason besides syntax why this isn't possible, base class initialization is just a function call. – Matteo Italia Apr 28 '17 at 13:34
  • @TobySpeight [this question](http://stackoverflow.com/questions/29986208/how-should-i-deal-with-mutexes-in-movable-types-in-c) covers how to move classes containing mutexes – M.M Apr 28 '17 at 13:38
  • @MatteoItalia : (I assume the following is no news to you, but it might be to others) it's more than just a function call - it's a function call that initializes the object. And the base class part of the object needs to be fully initialized *before* initializing the derived class part of the object (according to the standard). That's why it's not allowed to call the base class constructor directly from the derived class constructor, because that would make it hard to guarantee the initialization order. – Sander De Dycker Apr 28 '17 at 13:52
  • How about a basic constructor that both use which does only what those have in common, which is protected so that only Derived can access it, and some initialization methods? Like `Derived::Derived(bool init_with_string) : Base() { if(init_with_string){ Base::init("forty-two"); ...` – Aziuth Apr 28 '17 at 13:56
  • @Aziuth : unfortunately, the op states the `Base` class can't be modified (and it currently has neither a default constructor, nor convenient `init` methods) – Sander De Dycker Apr 28 '17 at 13:58

5 Answers5

12

A static function will work fine here

struct Base {
    Base(int i);
    Base(const char *sz);
    Base(const Base&) = delete;
    Base(const Base&&) = delete;
};

struct Derived : Base {
   using Base::Base;

   static Derived construct(bool with_string) {
      if(with_string) return { "forty-two" };
      return { 42 };
   }
};

Notice that this does not require a move, nor a copy constructor. If you want to have this as a local, you need to bind it to a reference in order to avoid moving it

auto &&w = Derived::construct(true);
auto &&wo = Derived::construct(false);
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Isn't copy elision guaranteed only from C++17? – rustyx Apr 30 '17 at 14:53
  • 2
    @RustyX The return-operand is not an expression, but an initializer list. It has always specified the initialization of the return value directly, without copies or moves, C++11 onwards. – Johannes Schaub - litb Apr 30 '17 at 14:56
  • Why `using Base::Base;` is necessary here? is it "unhiding" all available Base constructors, which won't be visible otherwise? – Kimi May 03 '17 at 08:43
5

Not ideal but I have used this technique when nothing better suggested itself, when the initialization of an object had to occur inside a code block:

// (Same base as your code)
#include <memory>

struct Derived : public Base {
    using Base::Base;
};

int main(int argc, char **argv)
{
    std::unique_ptr<Derived> pd;

    if ( argc == 2 )
    {
        pd = std::make_unique<Derived>(42);
    }
    else
    {
        pd = std::make_unique<Derived>("forty-two");
    }

    Derived &d = *pd;

   // use d, don't manually reset pd
}
M.M
  • 138,810
  • 21
  • 208
  • 365
  • Again, as said above this is just moving the problem around; in the real code the decision if one constructor or the other has to be called is quite complex and cannot be delegated to the caller; of course I could make a `make_Derived` function returning an appropriate `std::unique_ptr` and move all the logic there, but that's admitting defeat. – Matteo Italia Apr 28 '17 at 13:44
  • 8
    I don't think a factory method is "admitting defeat" - it's an established, well-understood pattern, and if done right, keeps the logic with the class it's creating. – Toby Speight Apr 28 '17 at 13:47
  • @TobySpeight: that's what I ended up implementing, but still, it forces to allocate on the heap, which in my case does perfectly fine, but is not necessarily a good solution in general. – Matteo Italia Apr 28 '17 at 14:24
1

Edit: The following approach is quite close to what you are after, using placement new:

struct Derived3 : Base
{
    Derived3::Derived3(bool init_with_string) : Base(42)
    {
        if(init_with_string)
        {
            // in case of any resources would have been allocated:
            this->~Derived3();
            new(this) Derived3("forty-two");
        }
    }
private:
    using Base::Base;
};

First I construct a Base object with one of the two types. If the type condition does not match, I need to destroy it again calling the destructor explicitly to avoid undefined behaviour (and to prevent leaks in case that Base allocated memory already). Afterwards, we can reconstruct the class using the other constructor. Well, and this is the drawback of this approach, we potentially allocate some memory in vain, just to release it afterwards again and then re-allocate it! At least, we create an object in vain in some of the cases.

So no non-plus-ultra solution, so I'll leave my previous approaches:

Currently offering two approaches, both not really what you are after, but for now, I did not get closer...

Well, the obvious and easy solution:

struct Derived1 : Base
{
    static Derived1* instance(bool init_with_string)
    {
        return init_with_string ? new Derived1("forty-two") : new Derived1(42);
    }
private: // or even not, then you can construct your derived classes directly...
    using Base::Base;
};
int main(int argc, char* argv[])
{
    Derived1* d1 = Derived1::instance(false);
}

Template variant:

struct Derived2 : Base
{
private:
    using Base::Base;
    template <bool>
    friend struct Derived2Maker;
};

template <bool InitWithString>
struct Derived2Maker : Derived2
{
    Derived2Maker() : Derived2(42) { }
};
template <>
struct Derived2Maker<true> : Derived2
{
    Derived2Maker() : Derived2("forty-two") { }
};
int main(int argc, char* argv[])
{
    Derived2* d2 = new Derived2Maker<false>();
}

Drawback: boolean parameter must be known at compile time...

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • 1
    Suppose we have `Derived2:Derived`. Is using placement new legal here? – Yakk - Adam Nevraumont Apr 28 '17 at 20:59
  • The first solution was another one I tried and in practice it works, but I'm not entirely sure about the correctness according to the standard. Still, upvoted. – Matteo Italia Apr 28 '17 at 21:20
  • @MatteoItalia C++17 standard, 6.8.5: "A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; [...]" – Aconcagua Apr 29 '17 at 02:30
  • @MatteoItalia Actually, life time of the object in construction has not even begun yet: C++ 6.8.1: "The lifetime of an object of type T begins when: (1.1) — storage with the proper alignment and size for type T is obtained, and (1.2) — if the object has non-vacuous initialization, its initialization is complete" From both, I conclude that we are fine. – Aconcagua Apr 29 '17 at 02:34
  • 1
    Shouldn't the placement-new be `new (static_cast(this)) Base(...)`? That way you're within the lifetime of the Base sub-object, and within the period where 6.8.5 allows you to destroy and recreate an object in-place, and avoids extra constructors for `Derived3`. However, if we're in a virtual hierarchy, that would leave the `Base` vtable incorrectly set. – TBBle Apr 30 '17 at 14:41
  • @TBBle The static-cast won't serve anything at all - placement new accepts a void* pointer anyway. The base object has already been constructed, thus I call the destructor to undo this part of the work. Then there is no living object in the memory any more, so I will have to start object construction right from the start, including construction of the Base object - but this is exactly what the constructor called from placemant new will do (and this includes creation of the vtable, too). – Aconcagua Apr 30 '17 at 17:08
  • @TBBle Side note vtable: Actually, the vtable is one and the same for all objects of the same type (at time of creation, not of the pointer or reference pointing to), so creation of the vtable is only setting the pointer to some memory, similar as if the vtable was a static variable... – Aconcagua May 01 '17 at 14:25
  • `class Derived : public Base1, public Base2 {};` will require you to placement-new then via `static_cast(this)` and `static_cast(this)`. The fact that in a standard-layout class, `static_cast(this) == this` is probably a dangerous thing to rely on blindly. – TBBle May 02 '17 at 15:11
  • The `Base` vtable is different in an object created by `new Base` and `new Derived`, unless `Derived` has no virtual method implementations. Hence calling `new (static_cast(this)) Base(...)` (or `new (this) Base(...)` if you like to live on the wild side) will put the `Base` vtable in your `Derived` object. Hilarity ensues. – TBBle May 02 '17 at 15:16
  • 1
    My concern with calling placement-`new` for `Derived` is that it'll also go looking for a `Derived` constructor, and you're *in* a `Derived` constructor now. An unlucky typo could leave that compiling happily, but recursive at run-time, perhaps for an unusual code-path. That said, as the final instruction of the constructor, recreating yourself over yourself is probably not too bad, but you do need to call your *own* destructor (`Derived::~Derived`), or any (for example) `unique_ptr<>` initialised in `Derived`'s member-initialisation list will leak. Which suggests a factory pattern for safety. – TBBle May 02 '17 at 15:19
  • @TBBle Well, your own comments proved that the leaving the cast for placement new using the *Derived* constructor is correct... You should be right, though, with requiring the cast for the *destructors*. Calling the own destructor for sure is most elegant, not sure, though, if legal, as the object pointed to by `this` has not yet been fully constructed (constructor did not yet finish execution). – Aconcagua May 02 '17 at 15:26
  • My point was that for a non-virtual object, destroying and creating the Base sub-object is correct and standards-compliant. For a virtual class Base, I think there's no way to do this without "exploring" undefined behaviour. That's pretty common when you have a virtual class, you really start to trigger undefined behaviour just from looking at it funny. – TBBle May 02 '17 at 15:31
  • 6.8.5: "A program may end the lifetime of any object by reusing the storage which the object occupies..." I did that by calling the placement new, ending lifetime of the current object. 6.8.5 again: "[...]the program is not required to call the destructor explicitly before the storage which the object occupies is reused [...] however, [...] and any program that depends on the side effects produced by the destructor has undefined behavior" That is why I *need* to call the base class destructors... – Aconcagua May 02 '17 at 15:53
  • 6.8.6: "after the lifetime of an object has ended and before the storage which the object occupied is reused [...] using the pointer as if the pointer were of type void*, is well-defined". -> placement new is fine. 6.8.8 "If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object [...] will automatically refer to the new object and, once the lifetime of the new object has started, can be used [...]" – Aconcagua May 02 '17 at 15:56
  • [... to modify ...] - conditions that follow apply for this, of course... So above code should be fine, apart from missing casts for the base class dtors. Would be nice to call the Derived dtor explicitly, but as stated, not sure if legal... – Aconcagua May 02 '17 at 15:58
  • @TBBle found it: 15.4.14 "The invocation of a destructor is subject to the usual rules for member functions (12.2.1); [...]" – if I don't misinterpret this, this should allow to call the own destructor from within the constructor... – Aconcagua May 02 '17 at 16:11
  • From [[basic.life]/5](http://eel.is/c++draft/basic.life#5), you _also_ need to call the destructors of any non-trivial member variables as well as all your Bases, in reverse order of declaration, or you're looking at undefined behaviour for them. See [[class.base.init]/13](http://eel.is/c++draft/class.base.init#13). I agree at this point `~Derived3(); new (this) Derived3{};` looks the most sane. See also the example in [[basic.life]/6.5](http://eel.is/c++draft/basic.life#6.5). Once you call `~Derived3();`, you can *only* use `this` as a `void*` to the storage until the end of the method. – TBBle May 02 '17 at 18:19
  • 2
    @TBBle Interesting: [basic.life:8](http://eel.is/c++draft/basic.life#8), which I referred to already. Actually, in the [example](http://eel.is/c++draft/basic.life#8.4) given, they do exactly what I do, just in the assignment operator, not in the constructor... Finally gluing ["allowed to call member functions"](http://eel.is/c++draft/class.base.init#16) and ["destructors following the usual rules of member functions"](http://eel.is/c++draft/class.dtor#14) together should finally make the whole stuff entirely legal... Have I overseen anything yet? – Aconcagua May 03 '17 at 06:34
  • Actually, I made one oversight above. You _can_ keep using `this` after the placement-new, as long as you don't fall-foul of http://eel.is/c++draft/basic.life#8.3. That's where [std::launder](http://en.cppreference.com/w/cpp/utility/launder) will help. See http://wg21.link/P0532 which is probably also relevant to this answer, describing places where it'll go wrong. – TBBle May 03 '17 at 10:50
1

It's not really the answer you're looking for, but if you can get to a point where the decision is exposed to the code calling the constructor, you can use a tag-dispatch template constructor with a generic lambda for constexpr-if (bingo!), as follows:

#include <type_traits>
#include <memory>

struct Base {
    Base(int) {};
    Base(const char *) {};
    Base(const Base&) = delete;
    Base(const Base&&) = delete;
};

struct Derived : Base{
    static std::unique_ptr<Derived> make_unique(bool init_with_string);

private:
    template<typename init_with_string_t>
    Derived(init_with_string_t);
};

template<typename init_with_string_t>
Derived::Derived(init_with_string_t) : Base([]{if constexpr(init_with_string_t::value) return "forty-two"; else return 42;}())
{
}

std::unique_ptr<Derived> Derived::make_unique(bool init_with_string)
{
    if (init_with_string)
        return std::unique_ptr<Derived>(new Derived(std::true_type{}));
    else
        return std::unique_ptr<Derived>(new Derived(std::true_type{}));
}

int main()
{
    auto d1 =  Derived::make_unique(true);
    auto d2 =  Derived::make_unique(false);
}

Live demo on wandbox

That's a C++17-heavy feature list, so clang 3.9 or gcc 7. As I did here, you can wrap up the tag-dispatch call with a factory, and if you make that a static member of the class, you can put the logic that selects whether to initialise with a string or a value in a private static class method, or inline in the factory method as you prefer.

Some less-than-pleasant issues I had to work around to get this working:

The type-dispatch is needed because even through we could have Derived's constructor be:

template<bool init_with_string>
Derived::Derived() : Base([]{if constexpr(init_with_string) return "forty-two"; else return 42;}())
{
}

there's actually no way to explicitly specify template parameters to a constructor, and template deduction cannot deduce non-type template parameters. (If you have access to a constexpr value in scope, you could probably do something like template<bool init_with_string = constexpr_bool_method()> but if you could do that, you could put that straight in the lambda.

The if constexpr lambda idea came from an answer to Equivalent ternary operator for constexpr if?, which would have been nicer.

Sadly, we can't use std::make_unique in a factory method to access private constructors, because friendship isn't transitive.

Community
  • 1
  • 1
TBBle
  • 1,436
  • 10
  • 27
  • If you have C++17, then just take advantage of guaranteed elision and do `Base(use_string ? Base("hello") : Base(42))`. – T.C. May 03 '17 at 07:06
  • Once I go with a factory method I don't see why I have to go through this template madness... I'll just add the relevant constructors to my class and use them from the factory method. – Matteo Italia May 03 '17 at 07:21
  • @T.C. Ah, thank you, that's much neater. I originally wanted constexpr ?: and found the constexpr lambda approach. I'd forgotten that guaranteed copy elision let me avoid the deleted copy-constructor here. Edit: Clang 3.9, 4.0 and HEAD and gcc HEAD all reject that, still looking for a copy constructor. Did I misimplement it? https://wandbox.org/permlink/ox41NAEKsfbd92Yz – TBBle May 03 '17 at 10:58
  • @MatteoItalia My approach here *only* makes sense if you have a lot of code in `Derived::Derived` that you don't want to duplicate with two constructors. Often, a factory pattern is the best approach when you want to do heavy things in a constructor anyway, particularly things that might be expected to fail or make choices. That said, the question was "Is this possible", not "should I try and do this?". I think this is a _slightly_ more elegant approach than destroy and recreating yourself. But only slightly. – TBBle May 03 '17 at 11:05
  • _If_ it turns out that clang and gcc fail to provide a guaranteed copy-elision in member initialisation, then as @T.C. notes we can get rid of the templates entirely, and do it dynamically: https://wandbox.org/permlink/aILfE0naO5bTS8cV – TBBle May 03 '17 at 11:12
  • Trivial reproduction of member-init copy/move-elision failing due to deleted copy/move-constructors: https://wandbox.org/permlink/WULpCTYNRmhbBBbe I can't see why this shouldn't work, per the comments on that text. gcc and clang are consistent here, are they both wrong? – TBBle May 03 '17 at 12:03
-1

Honestly you should just respect the implementation of Base and let Derived have two constructors.

class Derived : Base {
    Derived(int i) : Base(i) {}
    Derived(const char *s) : Base(s) {}
}

Then as Johannes Schaub remarked it's actually possible to have an external make_derived() using initializer lists even without copy constructors.

n.caillou
  • 1,263
  • 11
  • 15
  • Negating the problem is not a solution. Of course the example is just expository, the actual issue involves a base class that can either be initialized into an invalid but safe state (think of it as a sentinel value), or with a pointer to some resource. If my constructor manages to acquire the resource this last constructor has to be called, otherwise it has to be initialized to the invalid state through the default constructor. The acquisition of this resource is exactly what I'm trying to abstract away from the clients, so it wouldn't make sense to delegate it to the caller. – Matteo Italia May 03 '17 at 07:13
  • @MatteoItalia And yet, eventually the answer was to use an external higher-order wrapper (Johannes's static function), not to merge the two constructors in one. And it has to be this way because if you can't call the base constructors in the body of the derived constructors, they must be called in the initializer list, and thus the information must be in the parameters, and thus the information must be provided by the caller. Unless there's a way to insert a wrapper. – n.caillou May 03 '17 at 07:27
  • There's no technical reason why you couldn't do that into a constructor (and in fact it's routinely done in other languages, where the base constructor call is a regular function call that you have to perform yourself into the constructor), the only problem here is stupidly inflexible syntax (again, the whole initializer list concept which allows only for expressions and only in the declaration order is stupid AF). The whole point of this question was to understand if there was some way around it that I was missing, if the answer is "no" we all already know that I can use factory methods. – Matteo Italia May 03 '17 at 07:52
  • It is no indeed. You asked how to write `Derived`, I answered; `Base` wasn't designed in a flexible way and you're stuck with that. – n.caillou May 03 '17 at 08:06
  • @MatteoItalia: As described in one of the answers above, you _can_ destroy and recreate your base classes (or yourself) in the constructor. This is only possible _because_ the member initialiser list has ensured that when you're in your constructor body, all the base classes and member variables are in some known, sane state. I suggest (based on the other languages I know) that when you are allowed to defer constructor call-time into the child constructor as you describe, that's because you have the built-in equivalent of a member initialiser list which default-initialised all the bases aleady – TBBle May 03 '17 at 10:54