42

I've just got confused how to implement something in a generic way in C++. It's a bit convoluted, so let me explain step by step.


Consider such code:

void a(int) {
    // do something
}
void b(int) {
    // something else
}


void function1() {
    a(123);
    a(456);
}
void function2() {
    b(123);
    b(456);
}

void test() {
    function1();
    function2();
}

It's easily noticable that function1 and function2 do the same, with the only different part being the internal function.

Therefore, I want to make function generic to avoid code redundancy. I can do it using function pointers or templates. Let me choose the latter for now. My thinking is that it's better since the compiler will surely be able to inline the functions - am I correct? Can compilers still inline the calls if they are made via function pointers? This is a side-question.

OK, back to the original point... A solution with templates:

void a(int) {
    // do something
}
void b(int) {
    // something else
}

template<void (*param)(int) >
void function() {
    param(123);
    param(456);
}

void test() {
    function<a>();
    function<b>();
}

All OK. But I'm running into a problem: Can I still do that if a and b are generics themselves?

template<typename T>
void a(T t) {
   // do something
}

template<typename T>
void b(T t) {
   // something else
}

template< ...param... > // ???
void function() {
    param<SomeType>(someobj);
    param<AnotherType>(someotherobj);
}

void test() {
    function<a>();
    function<b>();
}

I know that a template parameter can be one of:

  • a type,
  • a template type,
  • a value of a type.

None of those seems to cover my situation. My main question is hence: How do I solve that, i.e. define function() in the last example?

(Yes, function pointers seem to be a workaround in this exact case - provided they can also be inlined - but I'm looking for a general solution for this class of problems).

Kos
  • 70,399
  • 25
  • 169
  • 233

4 Answers4

43

In order to solve this problem with templates, you have to use a template template parameter. Unfortunately, you cannot pass template template function as a type, because it has to be instantiated first. But there is a workaround with dummy structures. Here is an example:

template <typename T>
struct a {

    static void foo (T = T ())
    {
    }

};

template <typename T>
struct b {

    static void foo (T = T ())
    {
    }

};

struct SomeObj {};
struct SomeOtherObj {};

template <template <typename P> class T>
void function ()
{
    T<SomeObj>::foo ();
    T<SomeOtherObj>::foo ();
}

int main ()
{
    function<a>();
    function<b>();
}
  • 1
    Not exactly sure why this got downvoted. It's not entirely satisfactory but it solves the problem. – Chris Lutz Jan 15 '11 at 00:48
  • 2
    So to sum up: the only solution which would make it possible to inline the calls is to replace the functions with functors? A bit unwieldy, but entirely acceptable I think. Thanks! – Kos Jan 20 '11 at 13:14
  • 1
    But I admit that I'm very surprised after you said that it's not possible to inline a by-address call... If the address can be determined in compile time to be equal to a given function's address, I'd expect the compiler to be smart enough. :) Strange... – Kos Jan 20 '11 at 13:15
  • I've had a run with g++ 4.5 and got contrary results: http://pastebin.com/eXbAyLPv – Kos Jan 20 '11 at 14:34
  • @Kos: your test is way too simple, gcc is smart enough to optimize simple static blocks of code. Try something closer to reality. BTW, it doesn't make sense to specify `-O3` and `-Os` at the same time. If you use multiple -O options, with or without level numbers, the last such option is the one that is effective. –  Jan 20 '11 at 16:56
  • Simple indeed, but that's what I wanted to know - whether using indirection in form of function pointer gets optimized out or not when the function itself is known. Thanks for the info about optimization flags, I thought they somehow act together in terms of exact applied -f flags. – Kos Jan 20 '11 at 18:29
  • I gotta say, Vlad, thats gnarly, but wth, it works, and fits the bill. – WhozCraig Sep 26 '12 at 16:49
  • In C++14 one could use generic lambda expressions to generate those closures: makes the code a bit shorter and does not require template template arguments. – mariusm May 17 '18 at 12:36
  • @ChefGladiator e.g. C++14: https://godbolt.org/z/LYsBss see also C++17: https://en.cppreference.com/w/cpp/utility/variant/visit – mariusm Nov 07 '19 at 13:52
  • @ChefGladiator actually, see the answer by Jarod42 below https://stackoverflow.com/a/56399349/2685239 – mariusm Nov 07 '19 at 14:13
  • Can you explain what the line `template – Daniel S. Aug 16 '22 at 07:11
7

With generic lambda from C++14 you might do:

template<typename T> void a(T t) { /* do something */}
template<typename T> void b(T t) { /* something else */ }

template <typename F>
void function(F&& f) {
    f(someobj);
    f(someotherobj);
}

void test() {
    // For simple cases, auto&& is even probably auto or const auto&
    function([](auto&& t){ a(t); });
    function([](auto&& t){ b(t); });

    // For perfect forwarding
    function([](auto&& t){ a(std::forward<decltype(t)>(t)); });
    function([](auto&& t){ b(std::forward<decltype(t)>(t)); });
}

Can compilers still inline the calls if they are made via function pointers?

They can, but it is indeed more complicated, and they may fail more often than with functor or template.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

Here's a way. It may not be the best, but it works:

template <typename T, T param>
void function() {
    param(123);
    param(456);
}

void test()
{
    function< void(*)(int), a<int> >(); // space at end necessary to compiler
    function< void(*)(int), b<int> >(); // because the C++ grammar is ambiguous
}

Whether or not they'll be inlined depends on the compiler, but I would be rather surprised if they weren't.

EDIT: Okay, I'm a little off today and missed the part where the parameters are of different types. My bad.

There may be a tricky way to do this with templates, but this is the easiest way I could think of:

#define function(x) do { x<thing1>(obj1); x<thing2>(obj2) } while(0)

I know, I know, "macros are evil," blah blah blah. It works. If function needs to be more complicated than your example you may run into problems, but it is much easier than anything I've been able to come up with.

Chris Lutz
  • 73,191
  • 16
  • 130
  • 183
  • But please note that `function` will want to call different instantiations of the parameter template function. – Kos Jan 15 '11 at 00:15
  • @Kos - Is `#define` out of the question? – Chris Lutz Jan 15 '11 at 00:19
  • 1
    Well, it _is_ a "last resort" which would work :), but it's uncomfortable to edit, debug, can't be in a namespace... I'd prefer to find a template-based not preprocessor-based solution. – Kos Jan 15 '11 at 00:58
-2
template < typename F >
void function(F f)
{
  f(123);
}

void a(int x) { ... }

struct b { void operator() (int x) { ... } };

void outer()
{
  function(&a);
  function(b());
}
Edward Strange
  • 40,307
  • 7
  • 73
  • 125
  • 2
    This isn't what the OP wanted. – Chris Lutz Jan 15 '11 at 00:56
  • 2
    Thanks, but this is unrelated to the problem I've described. In this case, a given `function` call only uses 1 instantiation of a parameter function/functor. Please read the question once again. – Kos Jan 15 '11 at 00:57