0

DISCLAIMER - Honestly I am not sure how to frame this question or provide the proper context and would appreciate if people experienced with this problem could recommend what extra information I need to provide to clarify the context. And also if I need to clean up the code section below to make it more legible, I will do that as I receive comments!!

But anyway here goes -

I am trying to work with variadic templates but everytime I compile my code (its on a company codebase) the compiler (gcc 4.8.4 - C++11) seems to skip over all the variadic code section -

TestFactory.cpp

/* 
 * Use this design since Variadic Templates are not available in C++11
 * A MapHolder allows us to create a map of variadic functions.
 */
template <class... Args>
struct MapHolder
{
    static std::map<std::string, NpBaseTest*(*)(Args...)> CallbackMap;
};

template <class... Args>
std::map<std::string, NpBaseTest *(*)(Args...)> MapHolder<Args...>::CallbackMap;

class TestFactory
{
public:
    template <class... Args>
    static void RegisterTest(std::string name, NpBaseTest *(*callback)(Args...));

    template <class... Args>
    static NpBaseTest *CreateTest(const std::string &name, Args &&... args);
};

TestFactory.cpp

template <class... Args>
void TestFactory::RegisterTest(std::string name, NpBaseTest *(*callback)(Args...))
{
    MapHolder<Args...>::CallbackMap[name] = callback;
}

template <class... Args>
NpBaseTest *TestFactory::CreateTest(const std::string &name, Args &&... args)
{
    return (MapHolder<Args...>::CallbackMap[name])(std::forward<Args>(args)...);
}

Calling file -

    void np_test_mgr_print()
    {
        const char *s = "cavpkotest";
        std::string str(s);
        TestFactory::RegisterTest(str.c_str(), &CavPkoTest::create);
        NpBaseTest *o1{TestFactory::CreateTest<uint16_t>(str.c_str(), 1)};
        /* Irrelevant code section */
        NpTestMgr::get_instance().insert(o1);
        NpTestMgr::get_instance().submit();
    }
}

When I compile this (gcc 4.8.4) the object file TestFactory.o is empty. If I do this outside our codebase (gcc 4.4.6) the code is compiled and outputs -

[common]$ nm TestFactory.o | c++filt $1 | grep CreateTest
34:0000000000401d6a W NpBaseTest* TestFactory::CreateTest<unsigned short>(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned short&&)
[common]$ nm TestFactory.o | c++filt $1 | grep RegisterTest
35:0000000000401d40 W void TestFactory::RegisterTest<unsigned short>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, NpBaseTest* (*)(unsigned short))
too honest for this site
  • 12,050
  • 4
  • 30
  • 52
nitimalh
  • 919
  • 10
  • 26
  • 4
    Is there a reason for the `c`-tag or do you just find it aesthetically pleasing? – EOF Feb 09 '17 at 21:37
  • 1
    Templates are only instantiated *on use*. Putting the definition in an implementation without using them *in that file* will lead to no instantiation. Then, when you try to use it elsewhere, it will find no implementation to instantiate since it's in an implementation file that the current translation unit can't see. You should leave the implementation in the header. [See this question](http://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file). – François Andrieux Feb 09 '17 at 21:38
  • @FrançoisAndrieux I will give that a shot and provide an update ! Thanks for the link ! – nitimalh Feb 09 '17 at 22:19
  • @FrançoisAndrieux Since this is a variadic template how do I go about instantiating it ? I can take variable argument and types. If I specify the type am I not defeating the purpose of using variadic templates ? – nitimalh Feb 09 '17 at 23:33
  • @nitimalh I would assume that you intend to use your variadic templated function at some point, where you will know what types you need (even if those types are themselves template parameters). This is the point where you will instantiate. Instantiation only happens, by design, when you use the template. I feel like you are concerned that the symbols are not defined. Don't worry about it, they will be defined whenever you actually use your template, as long as your implementation is accessible (which it will be, if it's in your header). – François Andrieux Feb 09 '17 at 23:36
  • @FrançoisAndrieux I guess I am a little confused on how to do it. Can you give me an example of how I could instantiate it ( Just a basic example will suffice ) – nitimalh Feb 09 '17 at 23:37
  • @nitimalh I'll move my comment an answer and provide an example. – François Andrieux Feb 09 '17 at 23:38
  • @FrançoisAndrieux Thank you. Really appreciate your help ! – nitimalh Feb 09 '17 at 23:39

1 Answers1

1

Templates are only instantiated on use. Putting the definition in an implementation without using them in that file will lead to no instantiation. Then, when you try to use it elsewhere, it will find no implementation to instantiate since it's in an implementation file that the current translation unit can't see. You should leave the implementation in the header. See this question.

Your example works fine if have a definition for NpBaseTest and if you move all of the code in a header (TestFactory.h I assume). Here is an example of how to use the code in your example. Notice that the way your templates are written, they only accept pointers to functions that return NpBaseTest*.

// main.cpp
#include "TestFactory.h"
#include <iostream>

NpBaseTest* test_callback(int x, int y)
{
    std::cout << "Called test_callback(" << x << ", " << y << ")\n";
    return nullptr;
}

int main()
{
    // This instantiates TestFactory::RegisterTest<int, int>
    TestFactory::RegisterTest<int, int>("my test", &test_callback);

    // This instantiates TestFactory::CreateTest<int, int>
    NpBaseTest * result = TestFactory::CreateTest<int, int>("my test", 5, 10);

    return 0;
}

I've written the template parameters explicitly for clariy. In your case, the compiler will be able to deduce these parameters, and the example is greatly simplified. You can simply call the templated methods without any arguments and they will be deduced from the arguments.

// main.cpp
#include "TestFactory.h"
#include <iostream>

NpBaseTest* test_callback(int x, int y)
{
    std::cout << "Called test_callback(" << x << ", " << y << ")\n";
    return nullptr;
}

int main()
{
    // This instantiates TestFactory::RegisterTest<int, int>
    TestFactory::RegisterTest("my test", &test_callback);

    // This instantiates TestFactory::CreateTest<int, int>
    NpBaseTest * result = TestFactory::CreateTest("my test", 5, 10);

    return 0;
}

That's it. If try this example, you will see that symbols for TestFactory::RegisterTest<int, int> and TestFactory::CreateTest<int, int> are now generated.

Community
  • 1
  • 1
François Andrieux
  • 28,148
  • 6
  • 56
  • 87
  • Hey thanks for the detailed answer so that surely worked ! But one thing that boggles my mind is that I cannot find the `RegisterTest` or `CreateTest` symbols in any of the object files ??! I checked the `.o` file for the calling file (in your case `main.c`) and also `TestFactory.o` ? Any ideas ? – nitimalh Feb 10 '17 at 01:02
  • @nitimalh I'm not an expert on gcc, I can't answer with any confidence why you can't find the symbols. That might be grounds for another question. – François Andrieux Feb 10 '17 at 02:20