5

Suppose I have a header wrapper.h:

template <typename Func> void wrapper(const Func func);

and a file wrapper.cpp containing:

#include "wrapper.h"
template <typename Func>
void wrapper(const Func  func)
{
  func();
}

And a file main.cpp containing:

#include "wrapper.h"
#include <iostream>

int main()
{
  wrapper( [](){std::cout<<"hello."<<std::endl;} );
}

If I compile these together (e.g., cat wrapper.cpp main.cpp | g++ -std=c++11 -o main -x c++ -), I get no linker errors.

But if I compile them separately (e.g., g++ -std=c++11 -o wrapper.o -c wrapper.cpp && g++ -std=c++11 -o main main.cpp wrapper.o), I --- of course --- get a linker error:

Undefined symbols for architecture x86_64:
  "void wrapper<main::$_0>(main::$_0)", referenced from:
      _main in main-5f3a90.o

Normally, I could explicitly specialize wrapper and add something like this to wrapper.cpp:

template void wrapper<void(*)()>(void(*)())

But this particular template specialization doesn't work.

Is it possible to specialize a template on a lambda?

m.s.
  • 16,063
  • 7
  • 53
  • 88
Alec Jacobson
  • 6,032
  • 5
  • 51
  • 88
  • 1
    You could always convert the lambda to that function pointer type. – chris Jun 16 '16 at 03:13
  • 4
    Since the template code is in a `cpp` file, did you mean to say _explicit template instantiation_? – James Adkison Jun 16 '16 at 03:18
  • 2
    `template void wrapper(void(*)())` is **explicit instantiation** not **specialization** as you call it. There is a [difference](http://stackoverflow.com/a/4933205/1621391) between the two – WhiZTiM Jun 16 '16 at 04:01

3 Answers3

9

First, I assume you know about Why can templates only be implemented in the header file?

To your question:

Is it possible to specialize a template on a lambda?

Unfortunately No, template specializations work with exact match, and a lambda is a unique unnamed type. The problem is specializing for that type which you do not know.

Your best bet is to use std::function; or as you have done, then additionally force the lambda to be converted into a function pointer by adding +

int main()
{
  wrapper(+[](){std::cout<<"hello."<<std::endl;} );
}

Full example:

#include <iostream>

template <typename Func>
void wrapper(const Func  func)
{
    std::cout << "PRIMARY\n";
    func();
}

template <>
void wrapper<void(*)()>(void(*func)())
{
    std::cout << "SPECIALIZATION\n";
    func();
}

int main()
{
     wrapper([](){std::cout<<"hello\n"<<std::endl;} );
     wrapper(+[](){std::cout<<"world."<<std::endl;} );
}

This will print

PRIMARY
hello

SPECIALIZATION
world

Also, decltype facility wouldn't help, if it does, it will take away the flexibility of your need for lambda

Community
  • 1
  • 1
WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
  • users should know that this answer doesn't suits if you need to capture something in the lambda from the calling scope. – fiorentinoing Apr 06 '20 at 17:19
  • @fiorentinoing, if you need all sorts of hatchery, you should simply use `std::function<...>`, unless there some very very very rare performance concerns – WhiZTiM Apr 06 '20 at 17:34
  • if you should pass the callback to legacy code, you should pay attention to the captures. Conversion in capture presence is not implicit anymore. – fiorentinoing Apr 06 '20 at 17:36
0

Unfortunately you can't.

Your problem is that lambda types are randomly generated inside each compilation unit.

You can use functions across units because you can declare them in headers; then the linker will find the one with the correct name and type in the compilation unit which defines it. Same if you declare a variable, though less common. If the declaration resulted in a different type in each unit, this would fail and no interaction across units would be possible.

So if you try to make that lambda the "same" object in the two units (i.e. defining in header) the linking will fail because you cannot define the same object twice. And if you make them "different" objects (i.e. adding inline, or defining in source) they will have different types, so linking will fail to unify them as you would need to. Can't win.

Francesco Dondi
  • 1,064
  • 9
  • 17
0

Listen to : Why can templates only be implemented in the header file?

So when template definition is moved from file wrapper.cpp into headerfile wrapper.h, the wrapper() can be called by the suggested ways in main.cpp:

int main()
{
  wrapper( [](){std::cout<<"hello"<<std::endl;} );
  wrapper(+[](){std::cout<<"world"<<std::endl;} );
  wrapper(std::function<void()>( [](){std::cout<<"best"<<std::endl;} ));
}
Community
  • 1
  • 1
Roland
  • 336
  • 2
  • 8