0

I've been recently getting into template wizardry and in particular CRTP. I know that templates are used to make the compiler generate code for us so I was wondering if it were possible to make a template "decide" which parts of a function we would like it to include for a particular class. For example if I have the following code:

crtp.h

#include <iostream>
using std::endl;
using std::cout;

template<class T>
class A {
public:
    void func() {
        constexpr unsigned short mask = T::GetMask();
        if (mask & 1) {
            /*
            Do Something
            */
            cout << "Mask 1" << endl;
        }
        if (mask & 1 << 3) {
            /*
            Do Something else
            */
            cout << "Mask 2" << endl;
        }
    }
};

class B : public A<B> {
    friend class A<B>;
protected:
    static constexpr unsigned short GetMask() { return 0x0001; }
};

class C : public A<C> {
    friend class A<C>;
protected:
    static constexpr unsigned short GetMask() { return 0x0009; }
};

main.cpp

#include "ctrp.h"
#include <iostream>
#include <vector>

using std::cout;
using std::vector;
using std::getchar;
using std::endl;

int main() {
    B b;
    C c;
    cout << "B:" << endl;
    b.func();
    cout << endl << "C:" << endl;
    c.func();
    getchar();
}

Which when executed produces:

B:
Mask 1

C:
Mask 1
Mask 2

This works great, does exactly what I want it to. The problem is from my standpoint the if statements should be unnecessary. As I am dealing with constant expressions the compiler should have everything it needs to simply skip the branching and know to execute the first part for class B and both parts for class C.

I would like to cash in on this and specifically tell the compiler to remove the sections that are unnecessary for the particular class to avoid unnecessary branching at runtime. Unfortunately I have no idea how to do this, any ideas? Thanks in advance

Edit

In response to some of the awesome suggestions C++17's constexpr if expression is a near perfect solution that I had no idea existed, but am unfortunately unable to use. I am limited to using C++14.

Ryoku
  • 397
  • 2
  • 16

2 Answers2

1

If you care about performance, the compiler will very likely optimize out all "dead" branches and even the if condition, if it can evaluate it during compile time.

What is worse, all the branches need to be well formed until C++17 constexpr if. In this case, you can "outsource" the functionality to special (static member) functions and use specialization to invoke the right one. See @R Sahu's answer for the example.

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
0

Emulating if/else at compile time using template metaprogramming does not work that way. You have to imagine if/else using a different mindset.

Instead of

    if (mask & 1) {
        /*
        Do Something
        */
        cout << "Mask 1" << endl;
    }
    if (mask & 1 << 3) {
        /*
        Do Something else
        */
        cout << "Mask 2" << endl;
    }

you'll have to use something along the lines of:

   function1_selector<mask & 1>::dostuff();
   function2_selector<mask & 1 << 3 >::dostuff();

where

template <bool> struct function1_selector
{
  static void dostuff() { /* Do nothing */ }
};

template <> struct function1_selector<true> // Specialize for true
{
   static void dostuff() { /* Do something useful */ }
};

Add code for function2_selector similarly.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • This seems like its exactly what I can use. As a quick side note is there any extra overhead from using the selectors that I am not seeing. Or do they simply disappear like they look like they should when the compiler compiles everything? – Ryoku Feb 05 '18 at 20:58
  • @Ryoku, I didn't understand what you mean by *the selectors that I am not seeing* – R Sahu Feb 05 '18 at 21:00
  • Sorry what I meant by selectors was the function_selectors that you were using in your example. To rephrase, does using function1_selector and function2_selector templates in the way you have shown above incur a run time overhead I should be aware of? From my understanding the template will essentially paste the body of the functions where appropriate but am I misreading this, does it actually just leave a pointer to the appropriate dostuff() method? – Ryoku Feb 05 '18 at 21:27
  • @Ryoku, `function1_selector::dostuff()` is an empty inline function. I imagine a decent optimizing compiler will be able to optimize it away. `function1_selector::dostuff()` is an inline function too. However, depending on the amount and type of code in that function, a compiler might *really* inline it or make it a function call. I don't have any idea what a compiler will do with it. – R Sahu Feb 05 '18 at 21:36
  • sweet thanks for the help! I love how templates make things messier (like the multiple selector functions required) before they make them neater... Wait, what if I wanted to implement a function that's in the derived class (like a function within class B) within say `function1_selector` – Ryoku Feb 05 '18 at 22:10
  • @Ryoku, to pull that off, you have to: 1) Define a `virtual` member function in `A` and implement it in `B`. 2) Pass a reference to the object to `function1_selector::dostuff()`, 3) Make the `virtual` function call from `dostuff()`. – R Sahu Feb 05 '18 at 22:39
  • Is there any possible way to do it without virtual functions, to keep with the CRTP idea? – Ryoku Feb 05 '18 at 22:48
  • @Ryoku,Yes, you can. but then either `function1_selector` or `function1_selector::do_stuff()` needs to have another template parameter. When invoking the function, you'll need to use the same template parameter that was used to instantiate `A`. – R Sahu Feb 05 '18 at 23:02
  • Okay awesome. So if I do decide to go down this path I guess I will have to initialize new `function1_selector`s and `function2_selector`s for each class derived from A. Could I do that within the base class? As in have the base class generate new function selectors with the template parameters it received? – Ryoku Feb 05 '18 at 23:21
  • @Ryoku, you can't do that. That breaks the core philosophy of using templates. You write generic logic using the input parameters and use specializations to account for if/else scenarios. – R Sahu Feb 05 '18 at 23:25
  • Dang so what your saying is I have to create 2 function selectors for every derived class I make then? – Ryoku Feb 05 '18 at 23:40
  • Is there a particular name for this technique? If I was to make a guess this would be an example of template specialization – Ryoku Feb 06 '18 at 04:02