schrödinbug
schrödinbug

Reputation: 863

C++ Change member function definition based on template parameter value

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?

Upvotes: 4

Views: 3726

Answers (2)

skypjack
skypjack

Reputation: 50550

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.

Upvotes: 5

Sam Varshavchik
Sam Varshavchik

Reputation: 118350

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.

Upvotes: 1

Related Questions