Template Optimization

Sometimes part of the code can be better optimized by the compiler using the built-in implanted implementation for the invariant. For example, if you have a known number of channels in an image, instead of doing something like:

Image::doOperation() {
    for (unsigned int i = 0; i < numPixels; i++) {
        for (unsigned int j = 0; i j mChannels; j++) {
            // ...
        }
    }
}

You can do it:

template<unsigned int c> Image::doOperationInternal() {
    for (unsigned int i = 0; i < numPixels; i++) {
        for (unsigned int j = 0; j < c; j++) {
            // ...
        }
    }
}

Image::doOperation() {
    switch (mChannels) {
        case 1: doOperation<1>(); break;
        case 2: doOperation<2>(); break;
        case 3: doOperation<3>(); break;
        case 4: doOperation<4>(); break;
    }
}

which allows the compiler to generate different unrolled loops for different channel counts (which, in turn, can significantly increase the efficiency of execution, as well as open various optimizations, such as SIMD instructions, etc.).

case, , , case. , , enum Format ( ). , :

template<Image::Format f> Image::doOperationInternal() {
    for (unsigned int i = 0; i < numPixels; i++) {
        for (unsigned int j = 0; j < static_cast<unsigned int>(f); j++) {
            // ...
        }
    }
}

Image::doOperation() {
    const Format f = mFormat;
    doOperationInternal<f>();
}

( ) , f , , switch, .

, : , ?

+4
3

, . , , .

++ 11. ++ 1y auto: ++ 11 void.

functor :

struct example_functor {
  template<unsigned N>
  static void action(double d) const {
    std::cout << N << ":" << d << "\n"; // or whatever, N is a compile time constant
  }
};

++ 11 :

template<unsigned...> struct indexes {};
template<unsigned Max, unsigned... Is> struct make_indexes:make_indexes< Max-1, Max-1, Is... > {};
template<unsigned... Is> struct make_indexes<0, Is...>:indexes<Is...> {};

.

:

template<typename Functor, unsigned Max, typename... Ts>
void invoke_jump( unsigned index, Ts&&... ts );

:

invoke_jump<example_functor, 10>( 7, 3.14 );

:

template<typename Functor, unsigned... Is, typename... Ts>
void do_invoke_jump( unsigned index, indexes<Is...>, Ts&&... ts ) {
  static auto table[]={ &(Functor::template action<Is>)... };
  table[index]( std::forward<Ts>(ts)... )
}
template<typename Functor, unsigned Max, typename... Ts>
void invoke_jump( unsigned index, Ts&&... ts ) {
  do_invoke_jump( index, make_indexes<Max>(), std::forward<Ts>(ts)... );
}

static Functor::action, .

++ 03 ..., , . std::vector.

, Functor.action<I>() [, ] :

template<unsigned Begin, unsigned End, typename Functor>
struct ForEach:ForEach<Begin, End-1, Functor> {
  ForEach(Functor& functor):
    ForEach<Begin, End-1, Functor>(functor)
  {
    functor->template action<End-1>();
  }
};
template<unsigned Begin, typename Functor>
struct ForEach<Begin,Begin,Functor> {};

, ( ).

vector .

template<typename Signature, typename Functor>
struct PopulateVector {
  std::vector< Signature* >* target; // change the signature here to whatever you want
  PopulateVector(std::vector< Signature* >* t):target(t) {}
  template<unsigned I>
  void action() {
    target->push_back( &(Functor::template action<I>) );
  }
};

:

template<typename Signature, typename Functor, unsigned Max>
std::vector< Signature* > make_table() {
  std::vector< Signature* > retval;
  retval.reserve(Max);
  PopulateVector<Signature, Functor> worker(&retval);
  ForEach<0, Max>( worker ); // runtime work basically done on this line
  return retval;
}

std::vector.

Ith .

struct example_functor {
  template<unsigned I>
  static void action() {
    std::cout << I << "\n";
  }
};
void test( unsigned i ) {
  static std::vector< void(*)() > table = make_table< void(), example_functor, 100 >();
  if (i < 100)
    table[i]();
}

, i, , .

, , , i . action static, , static, .

++ 03 , , ( std::vector ).

scanline, , - scanline. , 1 .

- : .

+4

Yakk ++ 11/1y , ++ 03 - , / , , , switch switch :

#include<iostream>

using namespace std;

struct Foo {
    template<unsigned int c>
    static void Action() {
        std::cout << "c: " << c << endl;
    }
};

template<typename F>
void Dispatch(unsigned int c) {
    switch (c) {
    case 1: F::Action<1>(); break;
    case 2: F::Action<2>(); break;
    case 3: F::Action<3>(); break;
    }
}

int main() {
    for (int i = 0; i < 4; ++i)
        Dispatch<Foo>(i);
}
+1

Just for the sake of completeness, here is my (temporary) solution with which I visited in the meantime:

#define DISPATCH_TEMPLATE_CALL(func, args) do { \
    switch (mChannels) { \
    case 1: func<1> args; break; \
    case 2: func<2> args; break; \
    case 3: func<3> args; break; \
    case 4: func<4> args; break; \
    default: throw std::range_error("Unhandled format"); \
    } \
} while (0)

template<unsigned int n> void Image::doSomethingInternal(a, b, c) {
    // ...
}

void Image::doSomething(a, b, c) {
    DISPATCH_TEMPLATE_CALL(doSomethingInternal, (a, b, c));
}

which is obviously not the preferred approach. But it works.

0
source

Source: https://habr.com/ru/post/1525517/


All Articles