user109078
user109078

Reputation: 906

Is it possible in C++ to use the same code with and without compile time constants?

say you have a function like:

double do_it(int m)
{
   double result = 0;

   for(int i = 0; i < m; i++)
      result += i;

   return result;
}

If you know m at compile time you can do:

template<size_t t_m>
double do_it()
{
   double result = 0;

   for(int i = 0; i < t_m; i++)
      result += i;

   return result;   
}

This gives a possibility for things like loop unrolling when optimizing. But, sometimes you might know some cases at compile-time and some at run-time. Or, perhaps you have defaults which a user could change...but it would be nice to optimize the default case.

I'm wondering if there is any way to provide both versions without basically duplicating the code or using a macro?

Note that the above is a toy example to illustrate the point.

Upvotes: 4

Views: 193

Answers (3)

Eric
Eric

Reputation: 97571

Use constexpr (needs at least C++14 to allow for):

constexpr double do_it(int m)
{
   double result = 0;

   for(int i = 0; i < m; i++)
      result += i;

   return result;
}

constexpr double it_result = do_it(10) + 1;  // compile time `do_it`, possibly runtime `+ 1`

int main() {
    int x;
    cin >> x;
    do_it(x);  // runtime
}

If you want to force a constexpr value to be inlined as part of a runtime expression, you can use the FORCE_CT_EVAL macro from this comment:

#include <utility>
#define FORCE_CT_EVAL(func) [](){constexpr auto ___expr = func; return std::move(___expr);}()

double it_result = FORCE_CT_EVAL(do_it(10));  // compile time

Upvotes: 1

hegel5000
hegel5000

Reputation: 884

Yes you can, with std::integral_constant. Specifically, the following function will work with an int, as well as specializations of std::integral_constant.

template<class Num>
constexpr double do_it(Num m_unconverted) {
  double result = 0.;
  int m_converted = static_cast<int>(m_unconverted);
  for(int i = 0; i < m_converted; i++){ result += i; }
  return result;
}

If you want to call do_it with a compile-time constant, then you can use

constexpr double result = do_it(std::integral_constant<int, 5>{});

Otherwise, it's just

double result = do_it(some_number);

Upvotes: 3

Nicol Bolas
Nicol Bolas

Reputation: 473407

In terms of the language specification, there's no general way to have a function that works in the way you desire. But that doesn't mean compilers can't do it for you.

This gives a possibility for things like loop unrolling when optimizing.

You say this as though the compiler cannot unroll the loop otherwise.

The reason the compiler can unroll the template loop is because of the confluence of the following:

  1. The compiler has the definition of the function. In this case, the function definition is provided (it's a template function, so its definition has to be provided).

  2. The compiler has the compile-time value of the loop counter. In this case, through the template parameter.

But none of these factors explicitly require a template. If the compiler has the definition of a function, and it can determine the compile-time value of the loop counter, then it has 100% of the information needed to unroll that loop.

How it gets this information is irrelevant. It could be an inline function (you have to provide the definition) which you call given a compile-time constant as an argument. It could be a constexpr function (again, you have to provide the definition) which you call given a compile-time constant as an argument.

This is a matter of quality of implementation, not of language. If compile-time parameters are to ever be a thing, it would be to support things you cannot do otherwise, not to support optimization (or at least, not compiler optimizations). For example, you can't have a function which returns a std::array whose length is specified by a regular function parameter rather than a template parameter.

Upvotes: 5

Related Questions