C++17 static template lazy evaluation

I am familiar with lazy evaluation in C++, and would expect that the second branch of the if statement the generic fibonacci template would not be evaluated, when an argument < 0 is passed.

It doesn't need to be evaluated. But we aren't dealing with evaluation here. We are dealing with template instantiation. You used fibonacci<n-1>::value, and that requires the complete object type fibonacci<n-1> to be instantiated. The type has to be checked, to see if it has a member value that can be used in such an expression.

Instantiating a class template causes the declarations of its members to be instantiated. The declaration of the static data member contains an initializer, which must therefore be instantiated as well. So we hit the need to instantiate the template recursively.

Simply naming fibonacci<n-1> won't cause it to be instantiated (think forward declarations). If you want to delay the instantiation, you must delay using those types in a way that requires their definition (such as accessing a member).

The old meta-programming trick for this (that is very in line with functional programming) involves helper templates.

template<class L, class R>
struct add {
    static constexpr auto value = L::value + R::value;
};

template<int n>
struct fibonacci {
    static const int value = std::conditional_t<(n < 0), fibonacci<0>, add<fibonacci<n-1>, fibonacci<n-2>>>::value;
};

std::conditional_t will choose a type based on the condition. Then, the ::value of that type (and only that type) is accessed. So nothing is fully instantiated until actually needed.


You can use if constexpr:

template<int n>
struct fibonacci {
    static const int value = []() {
        if constexpr (n < 0) {
            return 0;
        } else {
            return fibonacci<n-1>::value + fibonacci<n-2>::value;
        }
    }();
};

When fibonacci is instantiated with some value of n, all expressions used inside this instantiation must be compiled as well. This means that any templates that are used must also be instantiated. This is necessary even if the expression containing the template instantiation is never evaluated.

The only way to avoid instantiation of a template inside an expression is to not compile the expression at all. This allows you to avoid instantiating the templates with incorrect arguments.

You can do this by using if constexpr from C++17:

template<int n>
struct fibonacci {
    static const int value = [] {
      if constexpr (n < 0) return 0;
       else return fibonacci<n-1>::value + fibonacci<n-2>::value;
    }();
};

Here's a demo.