3

Is it possible to conditionally compile a statement in a function based on the value of a template parameter? For example:

template<typename T, bool chk>
class subject
{
public:
    // the ideal case    
    void doSomething(T new_val)
    {
        if(chk)
        {
          if(new_val != val)
              //do_something only if new_val is different from val
        }
        else
        {
            //do_something even if new_val and val are equal
        }
    }


    //or if that's not possible, if chk = 0 use this method
    void doSomething(T new_val) 
    {
        //do_something even if new_val and val are equal
    }

    // and if chk = 1 use this method
    void doSomething(T new_val) 
    {
        if(new_val != val)
           //do_something only if new_val is different from val
    }

    T val;
};

the catch is based on the value of chk I don't even want the statement if(new_val!=val) compiled into the function (because then every type T used would have to have a != operator defined).

I guess one drawback to this approach is that foo<int,0> and foo<int,1> are different classes so it wouldn't be possible to define a function that doesn't care if chk is 0 or 1 (say watch(foo<int>)).

The application I'm looking at specifically is an observer and for some types I only want the observer to be notified if the value actually changes and for other types I want the observer to always be notified (and for those types I don't want to have to define a != operator).

Is this possible without having two separate classes?

schrödinbug
  • 692
  • 9
  • 19

2 Answers2

4

Is this possible without having two separate classes?

Yes, it is. If you don't want to specialize your class, so as to avoid code repetition, you can use a sfinae expression like the one in the following example:

#include <type_traits>
#include <iostream>

template<typename T, bool chk>
struct subject {
    template<bool trigger = chk>
    std::enable_if_t<trigger>
    doSomething(T new_val) {
        if(new_val != val) {
            std::cout << "new_val != val" << std::endl;
        } else {
            std::cout << "new_val == val" << std::endl;
        }
    }

    template<bool trigger = chk>
    std::enable_if_t<not trigger>
    doSomething(T new_val) {
        std::cout << "who cares?" << std::endl;
    }

    T val;
};

int main() {
    subject<int, true> s1{0};
    s1.doSomething(0);
    s1.doSomething(1);
    subject<int, false> s2{0};
    s2.doSomething(0);
    s2.doSomething(1);
}

The idea is that the right definition for doSomething is picked up at compile time and it depends on the value of the template parameter chk. The other definition is simply discarded as expected and won't be available at all.
Note that, for the sfinae expression to work, the trigger template parameter must be an actual parameter of the member function template. That's why you have to define it this way:

template<bool trigger = chk>
sfinae_expression_based_on_enable_if
doSomething(T new_val) { /* ... */ }

See it on running on coliru.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • This is great. I had looked at enable_if, but IMO the documentation for it on cppreference.com is rather incomprehensible and made it sound like it wasn't quite suitable for this (which could be because I don't really understand how SFINAE works) – schrödinbug Mar 06 '17 at 14:55
  • Actually would you mind adding a brief explanation of what enable_if_t gives you over just enable_if and maybe how it works? again I've looked but am coming up short. – schrödinbug Mar 06 '17 at 14:59
  • @schrödinbug You can find a lot of example here on SO. Explained better than what I can do!! ;-) – skypjack Mar 06 '17 at 16:31
0

What you are looking for is called 'template specialization'.

You will have to specialize your template. After defining your base template, as above, you will proceed and define its specialization:

template<typename T>
class subject<T, true>
{
public:

   // ...

You then proceed and define the entire subject class from scratch, making whatever changes you need, for the case where the second template parameter is true (or false, if that's what you need to be specialized). You can remove things, or add things, or change them completely. The specialized class can have different class members, or methods, or the same class methods, but which work completely differently.

It is important to understand that you are now defining the entire class, and not just the different bits. If only one minor aspect of the class really needs to be specialized this, of course, will result in a bunch of duplicated code; so it will often be necessary to refactor it, placing the varying bits into a helper class, or a function, and specialize just the varying bits.

The upcoming C++17 standard has some other alternatives to template specialization; but specialization is traditionally the first thing one learns, for these kinds of situations. So, you should open your C++ book to the chapter that discusses template specialization, and get the under the belt first, before forging ahead to the new stuff in C++17.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • I had looked at specialization, but as you mention it would require large amounts of duplication and I don't think it would allow a function to a take subject as on argument generically (i.e. regardless of whether chk was true or false.) I think you'd have to overload the function. Hmm. What features in C++17 are you referring to? – schrödinbug Mar 05 '17 at 03:14
  • You should reread the last half of my second paragraph. As far as C++17 goes, I was referring to [constexpr if](http://en.cppreference.com/w/cpp/language/if#Constexpr_If). – Sam Varshavchik Mar 05 '17 at 03:17