0

I am trying to use a not-type parametric template function as a member of a class and am running into errors. Below is a minimum working example

#include <iostream>

enum Mode {ka, kb, kab};

class Foo {
  public:
    Foo(const double& x = 1.0, const Mode& y = ka) : x_(x), y_(y) {;}

    Mode get() const{
      return y_;
    }
    template<Mode mode> double FooFunc();

  private:
    double x_;
    Mode y_;
};

template<> double Foo::FooFunc<ka>() {
  return 1/x_;
}

template<> double Foo::FooFunc<kb>() {
  return 1/x_/x_;
}

template<> double Foo::FooFunc<kab>() {
  return 1/x_/x_/x_;
}

int main() {
  Foo foo1(2, ka), foo2(2, kb), foo3(2, kab);

  std::cout << "foo1 function result: " << foo1.FooFunc<foo1.get()> << "\n";
  std::cout << "foo2 function result: " << foo2.FooFunc<foo2.get()> << "\n";                                                                                                                                                                                                  
  std::cout << "foo3 function result: " << foo3.FooFunc<foo3.get()> << "\n";

  return 0;
}

I am compiling this with clang as follows c++ foo.cpp -o foo -O3 -std=c++11. This results in the following errors and I am not sure what the issue is. Any help would be greatly appreciated.

foo.cpp:34:48: error: reference to non-static member function must be called
        std::cout << "foo1 function result: " << foo1.FooFunc<foo1.get()> << "\n";
                                                 ~~~~~^~~~~~~~~~~~~~~~~~~
foo.cpp:12:30: note: possible target for call
                template<Mode mode> double FooFunc();
                                           ^
foo.cpp:35:48: error: reference to non-static member function must be called
        std::cout << "foo2 function result: " << foo2.FooFunc<foo2.get()> << "\n";
                                                 ~~~~~^~~~~~~~~~~~~~~~~~~
foo.cpp:12:30: note: possible target for call
                template<Mode mode> double FooFunc();
                                           ^
foo.cpp:36:48: error: reference to non-static member function must be called
        std::cout << "foo3 function result: " << foo3.FooFunc<foo3.get()> << "\n";
                                                 ~~~~~^~~~~~~~~~~~~~~~~~~
foo.cpp:12:30: note: possible target for call
                template<Mode mode> double FooFunc();
                                           ^
3 errors generated.
bhache
  • 39
  • 5
  • 1
    a template argument must be a compile-time constant, `foo1.get()` is not. – RedFog May 08 '21 at 01:45
  • @RedFog, thank you. How could I modify such that it could be used at run-time without the use of either if else or switch case blocks? – bhache May 08 '21 at 01:54
  • 1
    to choose a compile-time branch by a runtime result, the only way is to use `switch` statement (including `if`-`else` statement), mapping table or virtual function. in your case, `switch` statement is definitely better. – RedFog May 08 '21 at 02:21
  • Also, FYI, no need to put a `;` into an empty function or method, simply `{}` will do. =) – Elliott May 09 '21 at 10:37

2 Answers2

3

The problem with your code is that a template argument has to be compile-time constant. For a function call this means it has to be constexpr while for a class method this means it has to be static.

You have only two options:

  • Either you leave the configuration of the Mode to run-time and allow the user to potentially re-configuring the Mode. For this you will have to introduce an if/else or switch statement inside the function.

    class Foo {
      public:
        constexpr Foo(double const x = 1.0, Mode const y = Mode::ka) noexcept
          : x_{x}, y_{y} {
          return;
        }
        Mode get() const noexcept {
          return y_;
        }
        double FooFunc() const {
          switch (y_) {
            case Mode::ka: 
              return 1.0/x_;
            case Mode::kb:
              return 1.0/x_/x_;
            case Mode::kab:
              return 1.0/x_/x_/x_;
            default:
              // Throw error
          }
        }
      private:
        double x_;
        Mode y_;
    };
    
  • If you do not plan to give the user the possibility to change the Mode on the fly you might make it a class template and specialise the corresponding function. This way there is though no possibility to change the mode on the fly.

    template <Mode M = Mode::ka>
    class Foo {
      public:
        constexpr Foo(double const x = 1.0) noexcept
          : x_(x) {
          return;
        }
        static constexpr Mode get() noexcept {
          return M;
        }
        double FooFunc() const noexcept;
      private:
        double x_;
        Mode y_;
    };
    
    template<>
    inline double Foo<Mode::ka>::FooFunc() const noexcept {
      return 1.0/x_;
    }
    template<>
    inline double Foo<Mode::kb>::FooFunc() const noexcept {
      return 1.0/x_/x_;
    }
    template<>
    inline double Foo<Mode::kab>::FooFunc() const noexcept {
      return 1.0/x_/x_/x_;
    }
    
2b-t
  • 2,414
  • 1
  • 10
  • 18
  • 1
    Thank you for a helpful and detailed answer! I ended up using template specializations. I was trying to avoid if else/switch statements in a for loop. – bhache May 10 '21 at 00:16
  • @bhache You are welcome! Another thing that I noticed is that you used a plain enum `enum Mode` instead of a scoped enum `enum class Mode`. Generally the latter should be preferred that is why I used `Mode::kab` instead of `kab` only. See e.g. [here](https://stackoverflow.com/a/18335862/9938686) for more details. – 2b-t May 10 '21 at 08:34
1

With constexpr, you might do:

enum Mode {ka, kb, kab};

class Foo {
public:
    constexpr Foo(const double& x = 1.0, const Mode& y = ka) : x_(x), y_(y) {}

    constexpr Mode get() const{
      return y_;
    }
    template<Mode mode> constexpr double FooFunc() const;

private:
    double x_;
    Mode y_;
};

template<> constexpr double Foo::FooFunc<ka>() const {
  return 1/x_;
}

template<> constexpr double Foo::FooFunc<kb>() const {
  return 1/x_/x_;
}

template<> constexpr double Foo::FooFunc<kab>() const {
  return 1/x_/x_/x_;
}

int main() {
  constexpr Foo foo1(2, ka), foo2(2, kb), foo3(2, kab);

  std::cout << "foo1 function result: " << foo1.FooFunc<foo1.get()>() << "\n";
  std::cout << "foo2 function result: " << foo2.FooFunc<foo2.get()>() << "\n";
  std::cout << "foo3 function result: " << foo3.FooFunc<foo3.get()>() << "\n";
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302