-13

I have a registry for lambda functions associated with a specific CommandId, where the registered function is supposed to create a concrete instance of a command executor class and supply it with some data:

CommandId.h

#include <cstdint>

enum class CommandId : uint16_t {
    Command1 ,
    Command2 ,
};

Registry.h

#include <map>
#include <functional>
#include <string>

#include "CommandId.h"

class Registry
{
public:
    static std::map<CommandId,std::function<void (std::string)>>& 
    GetCommands() {
        static std::map < CommandId, std::function<void(std::string)>> 
        theFunctionRegistry;
        return theFunctionRegistry;
    }
};

Now my idea was to provide concrete command implementations as derived classes from a CRTP template base, that provides a static member of itself just for the purpose of registering an associated lambda function (because the workflow for calling the derived classes will be just boilerplate code):

CommandBase.h

#include "CommandId.h"
#include "Registry.h"

// The general command execution interface
struct ICommand {
    virtual void Execute(const std::string& data) = 0;
    virtual ~ICommand() {}
};

template<typename Derived, CommandId CmdId>
class CommandBase : public ICommand
{
public:
    virtual ~CommandBase() {}
    void Register() {}
protected:
    // Dummy function to avoid abstract instantiations, should probably throw
    void Execute(const std::string& data) override {
    }

    // Protected constuctor meant for concrete command classes
    CommandBase(int& derivedRef) : derivedRef_(&derivedRef) {}

    // The static member that should perform the registation automagically
    static CommandBase<Derived, CmdId> registryAdder_;

    // This constructor is meant to be accessed from the above static member
    // instantiation only, and just register the lambda function 
    CommandBase() : derivedRef_(nullptr) {
        // Register a lambda function that ususe a concrete Derived instance
        Registry::GetCommands()[CmdId] = [](const std::string& data) {
            Derived derivedInstance;

            CommandBase<Derived, CmdId>* der = static_cast<CommandBase<Derived, CmdId>*>(&derivedInstance);
            // Manipulate the derived instances data and execute
            *(der->derivedRef_) = 42;
            der->Execute(data);
        };
    }

    // Provides access to the derived class instances data members and allows manipulation 
    int* derivedRef_;
};

template<typename Derived, CommandId CmdId>
CommandBase<Derived, CmdId> CommandBase<Derived, CmdId>::registryAdder_;

I thought that accessing the static registryAdder_ member of the base would force the compiler to provide an instantiation, but that doesn't happen.

Here's a trial for Command1 and Command2

<h3>Command1.h</h3>

#include "CommandBase.h"

class Command1 : public CommandBase<Command1, CommandId::Command1>
{
public:
    typedef CommandBase<Command1, CommandId::Command1> BaseClass;
    friend class BaseClass;

public:
    Command1(CommandId) {
        BaseClass::registryAdder_.Register();
    }
    virtual ~Command1() override {}

private:
    Command1() : BaseClass(member_)
    {
        BaseClass::registryAdder_.Register();
    }

    void Execute(const std::string& data) override {

    }
private:
    int member_;
};

Command2.h

#include "CommandBase.h"

class Command2 : public CommandBase<Command2, CommandId::Command2>
{
public:
    typedef CommandBase<Command2, CommandId::Command2> BaseClass;
    friend class BaseClass;

public:
    Command12CommandId) {
        BaseClass::registryAdder_.Register();
    }
    virtual ~Command1() override {}

private:
    Command2() : BaseClass(member_)
    {
        BaseClass::registryAdder_.Register();
    }

    void Execute(const std::string& data) override {

    }
private:
    int member_;
};

I was hoping that the line

BaseClass::registryAdder_.Register();

in the constructors would force the instantiation of the BaseClass::registryAdder_ static base class member. But it obviously doesn't, and the compiler just strips it away:

#include <iostream>
#include "Command1.h"
#include "Command2.h"
#include "Registry.h"

int main()
{
    std::cout << "There are " << Registry::GetCommands().size() << " registered commands." << std::endl;
    return 0;
}

Output:

There are 0 registered commands.

Now my question is:

How can I force the compiler to instantiate those static base class members from the CommandBase template class?


I've done some research of course, but these Q&A's didn't really satisfy me:

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • I could share the full VS2017 project via GitHub gist or other channels on demand. – πάντα ῥεῖ Jun 09 '17 at 19:11
  • Why are you returning a copy of an empty static map whenever someone calls `GetCommands`? In what way is that ... useful? I guess it would result in you always saying there are 0 commands each time you ask, which would explain your code. – Yakk - Adam Nevraumont Jun 09 '17 at 19:20
  • @Yakk THX for pointing out, that was simply a typo, when I was recreating the MCVE from my head. – πάντα ῥεῖ Jun 09 '17 at 19:22
  • 1
    Post actual [MCVE]s, not random code that looks sort of like one. Please generate a link to an online compiler that compiles your code and generates your symptoms, as your code mostly looking like a MCVE is clearly not evidence that you actually ran and checked it. – Yakk - Adam Nevraumont Jun 09 '17 at 19:24
  • @Yakk All of that code does compile. Let me stich that together at coliru or such, just give me a minute. (I know what I'm doing here!) – πάντα ῥεῖ Jun 09 '17 at 19:27
  • 1
    @πάνταῥεῖ You seem so sure, yet the line `Command12CommandId) {` and the virtual destructor `~Command1()` in the class `Command2` beg to differ. – François Andrieux Jun 09 '17 at 19:30
  • [Live example](http://coliru.stacked-crooked.com/a/04a42dd5c6d13f6b) with typos fixed and 2 command registered. You *must* check if your code is "plugged in", being confident in code you haven't actually tested does what you think it does simply doesn't work. I run into this problem all the time, and only the matra "is it actually plugged in" saves me. I have no idea how the code that generated your problem differs from the code I posted in the live example, and there is no way I can figure it out without you actually posting an actual MCVE. – Yakk - Adam Nevraumont Jun 09 '17 at 19:35
  • According to my debugger, there exists symbols for your commands' `registryAdder_`. It looks to me like all you have to do is finish your `CommandBase::Register` function. It's empty. – François Andrieux Jun 09 '17 at 19:37
  • @Yakk Well, let me go back to my labor again :-P – πάντα ῥεῖ Jun 09 '17 at 19:37
  • @Yakk Why not posting your fixes as an answer? At least my code compiled using VS2017 but didn't provide the expected output. Could it be there are differences regarding the actual compiler used? – πάντα ῥεῖ Jun 09 '17 at 19:44
  • @πάνταῥεῖ Because the fixes I found where typos unrelated to your problem. Some of them broke the compile. Some of them looked like dumb errors (like returning the copy of the map) that would explain your problem. With zero faith that your code causing the problem is actually the code you posted above, I could vote to close as "caused by a typo", or "code must include minimal example" but I cannot in good faith answer "it works for me". I still don't know if the code you are testing is the code above. Hence request for a link to a live example. – Yakk - Adam Nevraumont Jun 09 '17 at 19:57
  • @Yakk I'll try your fully fixed example with VS2017 now.. – πάντα ῥεῖ Jun 09 '17 at 20:06
  • @Yakk Yeah, that worked as intended. THX a lot. I'll inspect your fixes at my next working day and apply everything to the production code. Deleting the question again now. – πάντα ῥεῖ Jun 09 '17 at 20:14
  • Possibly duplicate of https://stackoverflow.com/questions/14620842/virtual-methods-in-constructor – ar2015 Aug 19 '18 at 09:00
  • @ar2015 No, that's a completely unrelated problem. – πάντα ῥεῖ Aug 19 '18 at 09:01
  • @πάνταῥεῖ, But I see it completely related. – ar2015 Aug 19 '18 at 09:02
  • @ar2015 I ever was aware that calling virtual methods from constructors won't work. CRTP is all about static polymorphism. – πάντα ῥεῖ Aug 19 '18 at 09:05
  • @πάνταῥεῖ, I think you do not understand its concept yet. You can study https://stackoverflow.com/questions/2391679/why-do-we-need-virtual-functions-in-c – ar2015 Aug 19 '18 at 09:06
  • @ar2015 Huh? What are you trying to tell me? I well understood what virtual and static polymorphism means, and how to use it with whatever kind of interfaces. I also know how that works at the machinery level with various programming languages like Java, C#, C or Delphi. – πάντα ῥεῖ Aug 19 '18 at 09:12
  • @πάνταῥεῖ, Why do you think you are different from the others? – ar2015 Aug 19 '18 at 09:12
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/178290/discussion-between---and-ar2015). – πάντα ῥεῖ Aug 19 '18 at 09:13

1 Answers1

1

I decided to reopen the question and self answer based on Yakk's fixes. Seems to be a more common problem (see here for example)

Yakk's example solved it:

#include <cstdint>

enum class CommandId : uint16_t {
    Command1 ,
    Command2 ,
};


#include <map>
#include <functional>
#include <string>

//#include "CommandId.h"

class Registry
{
public:
    static std::map<CommandId,std::function<void (std::string)>>& 
    GetCommands() {
        static std::map < CommandId, std::function<void(std::string)>> 
        theFunctionRegistry;
        return theFunctionRegistry;
    }
};

// #include "CommandId.h"
// #include "Registry.h"

// The general command execution interface
struct ICommand {
    virtual void Execute(const std::string& data) = 0;
    virtual ~ICommand() {}
};

template<typename Derived, CommandId CmdId>
class CommandBase : public ICommand
{
public:
    virtual ~CommandBase() {}
    void Register() {}
protected:
    // Dummy function to avoid abstract instantiations, should probably throw
    void Execute(const std::string& data) override {
    }

    // Protected constuctor meant for concrete command classes
    CommandBase(int& derivedRef) : derivedRef_(&derivedRef) {}

    // The static member that should perform the registation automagically
    static CommandBase<Derived, CmdId> registryAdder_;

    // This constructor is meant to be accessed from the above static member
    // instantiation only, and just register the lambda function 
    CommandBase() : derivedRef_(nullptr) {
        // Register a lambda function that ususe a concrete Derived instance
        Registry::GetCommands()[CmdId] = [](const std::string& data) {
            Derived derivedInstance;

            CommandBase<Derived, CmdId>* der = static_cast<CommandBase<Derived, CmdId>*>(&derivedInstance);
            // Manipulate the derived instances data and execute
            *(der->derivedRef_) = 42;
            der->Execute(data);
        };
    }

    // Provides access to the derived class instances data members and allows manipulation 
    int* derivedRef_;
};

template<typename Derived, CommandId CmdId>
CommandBase<Derived, CmdId> CommandBase<Derived, CmdId>::registryAdder_;

// #include "CommandBase.h"

class Command1 : public CommandBase<Command1, CommandId::Command1>
{
public:
    typedef CommandBase<Command1, CommandId::Command1> BaseClass;
    friend class CommandBase<Command1, CommandId::Command1>;

public:
    Command1(CommandId) {
        BaseClass::registryAdder_.Register();
    }
    virtual ~Command1() override {}

private:
    Command1() : BaseClass(member_)
    {
        BaseClass::registryAdder_.Register();
    }

    void Execute(const std::string& data) override {

    }
private:
    int member_;
};

// #include "CommandBase.h"

class Command2 : public CommandBase<Command2, CommandId::Command2>
{
public:
    typedef CommandBase<Command2, CommandId::Command2> BaseClass;
    friend class CommandBase<Command2, CommandId::Command2>;

public:
    Command2(CommandId) {
        BaseClass::registryAdder_.Register();
    }
    virtual ~Command2() override {}

private:
    Command2() : BaseClass(member_)
    {
        BaseClass::registryAdder_.Register();
    }

    void Execute(const std::string& data) override {

    }
private:
    int member_;
};

#include <iostream>
//#include "Command1.h"
//#include "Command2.h"
//#include "Registry.h"

int main()
{
    std::cout << "There are " << Registry::GetCommands().size() << " registered commands." << std::endl;
    return 0;
}

One needs to make use of these static instances in the derived classes.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190