2

I am creating a C++ class which takes certain parameters during initialization and has some functions based on its private variables, something like the compute function here:

class A {
  public:
    A(int x){
      a = x;
    }
    int compute(int y){
      if (a == 0){
        return y*y;
      }
      else if (a == 1){
        return 2*y;
      }
      else{
        return y;
      }
    }
  private:
    int a;
};

// usage

A myA(1); // private variables set only once
myA.compute(10); // this will check value of a 
myA.compute(1); // this will check value of a

Given that the private variables are set during initialization and will not be changed again, is there any efficient way to avoid the condition check related to the private variables during runtime?

Any and all assistance is appreciated. Thank you

Jarod42
  • 203,559
  • 14
  • 181
  • 302
sgp
  • 1,738
  • 6
  • 17
  • 31

4 Answers4

1

You could avoid the condition check if you would use e.g. a function object as a member, and set this conditioned on the value of variable a. Anyway, I don't think that the condition check will be big performance issue. But this will depend on your application of course.

#include <functional>
#include <iostream>

class A {
  public:
    A(int x)
    : a { x } 
    {
      if (a == 0){
        compute = [](int y){ return y*y; };
      }
      else if (a == 1){
        compute = [](int y){ return 2*y; };
      }
      else{
        compute = [](int y){ return y; };
      }

    }

    
    std::function<int(int)> compute;
    
  private:
    int a;
};

// usage


int main()
{
 
    A myA(1); // private variables set only once
    std::cout << myA.compute(10) << std::endl;
    std::cout << myA.compute(1) << std::endl;
    return 0;
}
F K
  • 315
  • 2
  • 9
  • `std::function` would be equivalent to virtual call, not sure it is faster than switching on `a` value in `compute`. – Jarod42 Dec 15 '20 at 10:52
1

You can template the function compute() on an int and use the template value as parameter. You can see the result at https://godbolt.org/z/14Mh4E

class A {
public:
    A(int x) {
        a = x;
    }
    template <int y>
    constexpr int compute() const {
        if (a == 0) {
            return y * y;
        }
        else if (a == 1) {
            return 2 * y;
        }
        else {
            return y;
        }
    }
private:
    int a;
};

// usage

A myA(1); // private variables set only once
myA.compute<10>(); // this will check value of a 
myA.compute<1>(); // this will check value of a
Loreto
  • 674
  • 6
  • 20
  • 1
    This means that the paramater y is known at compile time which is not desired, or is it? Moreover, since the member a is not a constexpr, the compute function will never be evaluated as constexpr, and thus the condition check will always be performed. – F K Dec 15 '20 at 10:00
0

You can guarantee the conditions are evaluated at compile time by using constexpr. Note that in this case you must use C++14 for constexpr compute(...), as multiple return statements are only suppoerted in constexpr functions after C++14.

#include <iostream>

class A {
  public:
    constexpr A(const int x): a(x) { }
    constexpr int compute(const int y) const {
      // Multiple return statements inside a constexpr function
      // requires C++14 or above.
      if (a == 0) {
        return y*y;
      }
      else if (a == 1) {
        return 2*y;
      }
      else {
        return y;
      }
    }
  private:
    int a;
};


int main() {
  constexpr A myA(1);
  constexpr int num = myA.compute(123);

  std::cout << num << std::endl;

  return EXIT_SUCCESS;
}

This page contains a good explanation of constexpr, as well as examples.

scupit
  • 704
  • 6
  • 6
  • Notice that you requires that both `myA` and `y`(123) to be contant expression, and also usage (`num`). – Jarod42 Dec 15 '20 at 10:13
0

If parameters are runtime value, I don't see an optimal way to avoid condition or jump.

You can trade your condition by virtual call:

struct A
{
    virtual ~A() = default;
    virtual int compute(int) = 0;
};

struct A0 { int compute(int y) override { return y * y; } };
struct A1 { int compute(int y) override { return 2 * y; } };
struct AN { int compute(int y) override { return y; } };

std::unique_ptr<A> makeA(int a)
{
    switch (a) {
        case 0: return std::make_unique<A0>();
        case 0: return std::make_unique<A1>();
        default: return std::make_unique<AN>();
    }
}

(compiler might devirtualize the call if type is known at compile time)

or "equivalent":

struct A
{
    int (*f)(int); // or even std::function<int(int)> f; if you need capture.

    A(int a) : f(a == 0 ? +[](int y) { return y * y; }
               : a == 1 ? +[](int y) { return 2 * y; }
                        : +[](int y) { return y; })
    {}

    int compute(int y) { return f(y); }
};

(erased-type is harder for compiler to devirtualize)

Jarod42
  • 203,559
  • 14
  • 181
  • 302