c++templatescompiler-errorstemplate-argument-deductionstructured-bindings

Function as template argument that has a different structure depending on method


I am writing a routine to find the numerical roots of a function in C++. Depending on the algorithm, I can supply either the function or both the function and derivative. For example, I have two separate routines

template <typename T, typename Func>
T Bisection(Func func, T x) {
  // algorithm details...

  auto f = func(x);

  // more details...
}

template <typename T, typename Func>
T Newton(Func func, T x) {
  // algorithm details...

  auto [f, df] = func(x);

  // more details...
}

Note that one method assumes that the Func type will be a simple one-variable function, whereas the other assumes it is some structure that contains both the function 'f' and the derivative 'df'. Both of these routines seem to work for my tests so far.

I would like to write a master routine which accepts a parameter method, which selects either of these with a switch statement. What I have in mind is:

enum Method {
  Bisection, Newton
};

template <typename T, typename Func>
T find_root(Func func, T x, Method method) {
  switch(method) {
  case Bisection: return Bisection(func, x);
  case Newton: return Newton(func, x);
  default: // do other stuff...
  }
}

This does not compile. If I write a routine with method = Bisection for example, I get error error: cannot decompose non-array non-class type 'const float' because the Newton method needs a different structure for the binding, even though I am not using it. Is there a generic way I can circumvent this to allow for a uniform master method? I have another method which the user supplies the function and first and second derivative, so that structure has three components.


Solution

  • The problem is that in your case you are instantiating both functions for the types that doesn't fit the implementation. You may try if constexpr, but for this case you need to move the method parameter the template parameters:

    enum class Method {
        Bisection, Newton
    };
    
    template <Method method, typename T, typename Func>
    T find_root(Func func, T x) {
        if constexpr (method == Method::Bisection) {
            return Bisection(func, x);
        }
        else if constexpr (method == Method::Newton) {
            return Newton(func, x);
        }
        else {
        }
    }
    

    In this case the compiler will not instantiate the functions for the branches where expression is false (note that for the compiler to eliminate the false branches the expression needs to depend on the template parameters).