views:

4785

answers:

6

Hi, I have the following problem using template instantiation [*].

file foo.h

class Foo
{
public:
    template <typename F>
    void func(F f)

private:
    int member_;
};

file foo.cc

template <typename F>
Foo::func(F f)
{
     f(member_);
}

file caller.cc

Foo::func(boost::bind(&Bar::bar_func, bar_instance, _1));

While this compiles fine, the linker complains about an undefined symbol:

void Foo::func<boost::_bi::bind_t...>

How can I instantiate the function Foo::func? Since it takes a function as argument, I am little bit confused. I tried to add an instantiation function in foo.cc, as I am used to with regular non-function types:

instantiate()
{
    template<> void Foo::func<boost::function<void(int)> >(boost::function<void(int)>);
}

Obviously, this does not work. I would appreciate if someone can point me in the right direction.

Thanks!

[*] Yes, I read the parashift FAQ lite.

+1  A: 

Are you including foo.cc into caller.cc. Instantiation is something that happens at compile time -- when the compiler sees the call in caller it makes instantiated versions of the templates but needs to have the full definition available.

Lou Franco
... or when you explicitly instantiate it.
David Rodríguez - dribeas
+4  A: 

The answer to this is compiler dependent. Some versions of the Sun C++ compiler would handle this automatically by building a cache of template function implementations that would be shared across separate translation units.

If you're using Visual C++, and any other compiler that can't do this, you may as well put the function definition in the header.

Don't worry about duplicate definitions if the header is included by multiple .cc files. The compiler marks template-generated methods with a special attribute so the linker knows to throw away duplicates instead of complaining. This is one reason why C++ has the "one definition rule".

Edit: The above comments apply in the general case where your template must be capable of linking given any type parameters. If you know a closed set of types that clients will use, you can ensure they are available by using explicit instantiation in the template's implementation file, which will cause the compiler to generate definitions for other files to link against. But in the general case where your template needs to work with types possibly only known to the client, then there is little point in separating the template into a header file and and implementation file; any client needs to include both parts anyway. If you want to isolate clients from complex dependencies, hide those dependencies behind non-templated functions and then call into them from the template code.

Daniel Earwicker
...following the edit: the result of boost::bind is an 'undefined' type, so explicit template instantiation in this case will not be a good solution (you can read bind.hpp implementation and determine the real type for the template instantiation, but then updates to the bind library can possibly break it as the type is not part of the interface).
David Rodríguez - dribeas
A: 

@Lou: Including foo.cc into caller.cc causes unfortunately quite some havoc. Is it common practice to move the templated function into its own *.cc file, so that it can be included separately by translation units that need to see its definition?

@Earwicker: I'm not sure if I understood you correctly. You advocate to move the implementation to the header because the compiler ignores duplicate definitions? The implementation, foo.cc, contains several dependencies (which do not exist in my simplified example). I try to keep the header and the number of includes as small as possible, to reduce compile time and dependencies. Thus, I would prefer to let the definition in foo.cc.

Is it generally possible to instantiate the template? The fact that I am dealing with a function and not a first-class type confused my existing instantiation practices.

Matthias Vallentin
Yes it is possible to force instantiation without using. See Below.
Martin York
I added a paragraph to hopefully clarify.
Daniel Earwicker
+1  A: 

I think what they're both referring to is that template function definitions (not just declarations) have to be included in the file where they're used. Template functions don't actually exist unless/until they're used; if you put them in a separate cc file, then the compiler doesn't know about them in the other cc files, unless you explicitly #include that cc file into either the header file or the file that's calling them, due to the way the parser works.

(That's why template function definitions are generally kept in the header files, as Earwicker described.)

Any clearer?

Head Geek
Actually you can force an instantiation without using it. See below where I split code into three files and instantiate without using it.
Martin York
It is a common mistake to assume that as in most cases templates are defined in the header file it must be so.
David Rodríguez - dribeas
If you plan to use them in more than one .cpp file, you *must* define them in a header file. I'm sure there are some labored exceptions to that rule, but it's a rule for a good reason.
Head Geek
A: 

I believe Earwicker is correct. The problem with explicitly instantiating the template member function func in this case is that the type returned by boost::bind is implementation dependent. It is not a boost::function. A boost::function can contain a boost:bind because it has a template assignment operator that deduces the type of the right-hand side (the boost::bind result). In this particular use of func in caller.cc, with this particular implementation of boost, the type of the boost::bind is actually they type mentioned in the linker error between the < and > (ie. boost::_bi::bind_t...). But explicitly instantiating func for that type probably will have portability problems.

SCFrench
Sorry, but that shouldn't be a problem. I've done the kind of thing he's doing lots of times, on both Windows and Linux systems, with no problem.
Head Geek
I stand by my statement. look at the "knarly typedef" in Martin York's post. There's no way you can replace myfunc with boost::function and get it to work.
SCFrench
@Head Geek: this answer is about the 'unknown type' returned by bind -which means a couple of things, first that it is not a boost::function<>, then that it can be changed at any time by the library implementor as it is not part of the public interface -they are not bound to return the same type in the next boost release.
David Rodríguez - dribeas
+1  A: 

Splitting it into files Like you want:
Not that I recommend this. Just showing that it is possible.

plop.h

#include <iostream>
class Foo
{
public:
    Foo(): member_(15){}


    // Note No definition of this in a header file.
    // It is defined in plop.cpp and a single instantiation forced
    // Without actually using it.
    template <typename F>
    void func(F f);

private:
    int member_;
};


struct Bar
{
     void bar_func(int val) { std::cout << val << "\n"; }
};

struct Tar
{
    void tar_func(int val) { std::cout << "This should not print because of specialisation of func\n";}
};

Plop.cpp

#include "plop.h"
#include <boost/bind.hpp>
#include <iostream>

template <typename F>
void Foo::func(F f)
{
     f(member_);
}

// Gnarly typedef
typedef boost::_bi::bind_t<void, boost::_mfi::mf1<void, Bar, int>, boost::_bi::list2<boost::_bi::value<Bar>, boost::arg<1> (*)()> > myFunc;

// Force the compiler to generate an instantiation of Foo::func()
template void Foo::func<myFunc>(myFunc f);

// Note this is not a specialization as that requires the <> after template.
// See main.cpp for an example of specialization.

main.cpp

#include "plop.h"
#include <boost/bind.hpp>
#include <iostream>

// Gnarly typedef
typedef boost::_bi::bind_t<void, boost::_mfi::mf1<void, Tar, int>, boost::_bi::list2<boost::_bi::value<Tar>, boost::arg<1> (*)()> > myTar;

// Specialization of Foo::func()
template<> void Foo::func<myTar>(myTar f)
{
    std::cout << "Special\n";
}
// Note. This is not instantiated unless it is used.
// But because it is used in main() we get a version.

int main(int argc,char* argv[])
{
    Foo f;
    Bar b;
    Tar t;

    f.func(boost::bind(&Bar::bar_func, b, _1)); // Uses instantiation from plop.cpp
    f.func(boost::bind(&Tar::tar_func, t, _1)); // Uses local specialization
}
Martin York
The problem I see with this solution is that the concrete type returned by boost::bind is not part of their public interface (docs). In the docs (boost::bind) it says that it is an 'unknown type', and to me that means that the concrete type (shown above) should not be used and that the type can be changed at any given time (breaking the code above).
David Rodríguez - dribeas