2

I am trying to write a function such that if called with compile-time-constant arguments it will trigger a compile-time error if the value of the argument doesn't match a static_assert, but can still be called at run-time with computed values.

Something kinda like this:

template<int N> void f(N){
  static_assert(N == 5, "N can only be 5.");
  do_something_with(N);
}

void f(int N){
  if(N == 5){
    do_something_with(N);
  }
}

volatile int five = 5;
volatile int six = 6;

int main() {  
  f(5); //ok
  f(6); //compile-time error
  f(five); //ok
  f(six); //run-time abort

  return 0;
}

How can I do this?

Also, if possible I would like to be able to keep the simple f(something) syntax, because this code is intended for a library that should be usable by beginner programmers that aren't familiar with template syntax.

max66
  • 65,235
  • 10
  • 71
  • 111
AJMansfield
  • 4,039
  • 3
  • 29
  • 50
  • 2
    values passed to function as parameter cannot be deduced so the line `template void f(N){` cannot be correct – W.F. Feb 23 '17 at 19:41
  • 1
    Compile time or Run-time. You have to choose (or make two functions). – xinaiz Feb 23 '17 at 19:44
  • Is there a way to do it with `constexpr` rather than template stuff? – AJMansfield Feb 23 '17 at 19:46
  • @BlackMoses It's not strictly necessary for the function to be able to do a run-time error/exception, but it needs to be possible to call it with values that aren't available at compile time. – AJMansfield Feb 23 '17 at 19:50

2 Answers2

6

The best I can imagine is a constexpr function that throw an exception.

If executed at compile time, the throw cause a compilation error; if executed at run time, throw the exception

Someting like

#include <stdexcept>

constexpr int checkGreaterThanZero (int val)
 { return val > 0 ? val : throw std::domain_error("!"); }

int main()
 {
   // constexpr int ic { checkGreaterThanZero(-1) }; // compile error

   int ir { checkGreaterThanZero(-1) }; // runtime error
 }

-- EDIT --

As pointed by yuri kilocheck, instead of throwing an exception, you can call std::abort(); by example

constexpr int checkGreaterThanZero (int val)
 { return val > 0 ? val : (std::abort(), 0); }
max66
  • 65,235
  • 10
  • 71
  • 111
  • 1
    `std::abort` is ok too. But such function needs to be called in constexpr context to be evaluated at compile time. – yuri kilochek Feb 23 '17 at 19:50
  • @yurikilochek - I'm used to use `throw`, to solve this sort of problems, and I didn't think about it; you're right; thanks; modified my answer according it – max66 Feb 23 '17 at 19:59
3

With a different syntax, you may do:

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

namespace detail {
    template <std::size_t N>
    constexpr int to_number(const int (&a)[N])
    {
        int res = 0;
        for (auto e : a) {
            res *= 10;
            res += e;
        }
        return res;
    }
}

template <char ... Ns>
constexpr int_c<detail::to_number({(Ns - '0')...})> operator ""_c ()
{
    return {};
}

#if 1 // Write this way
// Compile time
template <int N> void f(int_c<N>) = delete;
void f(int_c<5>) { do_something_with(5); }

#else // Or like this
// Compile time
template <int N>
void f(int_c<N>)
{
    static_assert(N == 5, "!");
    do_something_with(N);
}

#endif

// Runtime
void f(int N){
    if (N == 5) {
        std::abort(); // Or any other error handling
    }
    f(5_c);
}

int main(){
    f(5_c); // ok
    // f(6_c); // Won't compile
    f(5); // ok
    f(6); // abort at runtime
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302