Marius Herzog
Marius Herzog

Reputation: 597

Overload function for arguments (not) deducable at compile time

Is there a way to overload a function in a way to distinguish between the argument being evaluable at compile time or at runtime only?

Suppose I have the following function:

 std::string lookup(int x) {
     return table<x>::value;
 }

which allows me to select a string value based on the parameter x in constant time (with space overhead). However, in some cases x cannot be provided at compile time, and I need to run a version of foo which does the lookup with a higher time complexity.

I could use functions with a different name of course, but I would like to have an unified interface.


I accepted an answer, but I'm still interested if this distinction is possible with exactly the same function call.

Upvotes: 4

Views: 292

Answers (3)

Dan Stahlke
Dan Stahlke

Reputation: 1469

There is also this trick:

std::string lookup(int x) {
    switch(x) {
    case 0: return table<0>::value;
    case 1: return table<1>::value;
    case 2: return table<2>::value;
    case 3: return table<3>::value;
    default: return generic_lookup(x);
}

This sort of thing works well when it's advantageous, but not required, for the integer to be known at compile time. For example, if it helps the optimizer. It can be hell on compile times though, if you're calling many instances of some complicated function in this way.

Upvotes: 0

ildjarn
ildjarn

Reputation: 62975

I believe the closest you can get is to overload lookup on int and std::integral_constant<int>; then, if the caller knows the value at compile-type, they can call the latter overload:

#include <type_traits>
#include <string>

std::string lookup(int const& x)                   // a
{
    return "a"; // high-complexity lookup using x
}

template<int x>
std::string lookup(std::integral_constant<int, x>) // b
{
    return "b"; // return table<x>::value;
}

template<typename T = void>
void lookup(int const&&)                           // c
{
    static_assert(
        !std::is_same<T, T>{},
        "to pass a compile-time constant to lookup, pass"
         " an instance of std::integral_constant<int>"
    );
}

template<int N>
using int_ = std::integral_constant<int, N>;

int main()
{
    int x = 3;
    int const y = 3;
    constexpr int z = 3;
    lookup(x);         // calls a
    lookup(y);         // calls a
    lookup(z);         // calls a
    lookup(int_<3>{}); // calls b
    lookup(3);         // calls c, compile-time error
}

Online Demo

Notes:

  • I've provided an int_ helper here so construction of std::integral_constant<int> is less verbose for the caller; this is optional.
  • Overload c will have false negatives (e.g. constexpr int variables are passed to overload a, not overload c), but this will weed out any actual int literals.

Upvotes: 2

SergeyA
SergeyA

Reputation: 62593

One option would be to use overloading in a similar manner:

template <int x> std::string find() {
   return table<x>::value;
}

std::string find(int x) {
    return ...
}    

Upvotes: 2

Related Questions