0
votes

I can see constexpr is winning more and more territory and one of the arguments for its use over template metaprogramming (TMP) is performance. My questions are:

  1. Are there any examples of performance comparisons in this regard?

  2. And why not replace TMP with constexpr if it is better?

TMP guarantees that it is run compile-time, but that can be done with constexpr too. Constexpr handles floating point numbers, which TMP doesn't and for all the examples I've seen, constexpr functions that replace TMP are easier to read and use less lines of code. Also constexpr allows conditions and loops while TMP only handles recursive "simulations" of loops.

I've asked in here earlier (link) about a related question - where I have 3 different ways of doing metaprogramming - here the constexpr function-way is the preferred..

1
Suggestion for a third alternative: 3. And why not mix TMP and constexpr, according to the circumstances? - max66
@max66 isn't that what I've done in one of my examples? Still I assume from what I've seen that constexpr functions are still preferable compared to TMP or TMP mixed with constexpr. - badaboomskey
@badaboomskey: "And why not replace TMP with constexpr if it is better?" The C++ committee is trying very hard to do so. But constexpr coding isn't as capable as TMP at the moment. - Nicol Bolas
@badaboomskey: Examples like factorial computation are just toys. TMP exists primarily to do type computations. TMP is what allows tuple_element_t to extract one of the types from a tuple type. Pure constexpr code can't do that; you can build a constexpr interface to type computations, but the meat of the work is going to get shelled out to some form of TMP. - Nicol Bolas
constexpr advantage is mainly code readability and reduce compiler overhead in some cases (for ex. the typical compile time factorial would probably take less time to compile than recursive templates). Equivalent code should have similar performance and except if you have a serious performance problem, you should use whatever make the code easier to read. - Phil1970

1 Answers

1
votes

constexpr function are generally more readable as they are regular functions.

Caveats that it can be used in runtime even with compile time constant argument (at call site).

constexpr int f(int n) { /*...*/ }

constexpr int f5 = f(5); // Compile time.
int f4 = f(4); // Runtime time (compiler can optimize as for non-constexpr functions though)

For runtime performance, there are similar when used in constant expression, as all is done at compile time.

For compile time performance, issue of TMP is the number of instantiations whereas constexpr function (with regular parameter) generally use less instantiation, So TMP makes generally compilation longer.

But, in case memoization is required (as for Fibonacci with recursive implementation)

constexpr std::size_t fibonacci(std::size_t n)
{
    if (n < 2) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}
template <std::size_t N> struct Fibonacci
{
    constexpr std::size_t value = Fibonacci<N - 2>::value + Fibonacci<N - 1>::value;
};
template <> struct Fibonacci<0>
{
    constexpr std::size_t value = 1;
};
template <> struct Fibonacci<1>
{
    constexpr std::size_t value = 1;
};

TMP should reuse instantiation whereas constexpr function might recompute it each time. In that case TMP will compile faster.

And why not replace TMP with constexpr if it is better?

First, we won't drop TMP which is used, for retro-compatibility.

Then, TMP is generally more adapted to return type or several types.

template <typename T> struct add_pointer
{
    using type = T*;
};

using int_ptr = add_pointer<int>::type;

whereas constexpr would require decltype and/or std::declval

template <typename T> struct Tag { using type = T; };
constexpr Tag<T*> add_pointer(Tag<T>) {return {}; }

using int_ptr = decltype(add_pointer(Tag<int>{})::type;