0

I am sure this question has been asked already but I couldn't find the answer.

If I have a function, let's say:

int Power(int number, int degree){
    if(degree==0){
        return 1;
    }
    return number*Power(number, degree-1);
}

It works only when the degree is a non-negative int. How can I prevent this function from being called with wrong parameters?

For example, if the programmer writes cout<<Power(2, -1);, I want the compiler to refuse to compile the code and return some kind of an error message (e.g. "function Power accepts only non-negative integers").

Another alternative would be for the function to not return any value in this case. For example:

int Power(int number, unsigned int degree){
    if(degree<0){
        //return nothing
    }
    if(degree==0){
        return 1;
    }
    return number*Power(number, degree-1);
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Kotaka Danski
  • 451
  • 1
  • 4
  • 14
  • OT: Just a terminology note — a function hasn't a wrong _parameter_, but a wrong _argument_ instead. – Daniel Langr Dec 14 '20 at 16:17
  • 1
    @DanielLangr I've seen "parameter" and "argument" used interchangeably. – Etienne de Martel Dec 14 '20 at 16:17
  • 5
    You declared `degree` in the second example as `unsigned int`, so `degree < 0` cannot be true. – mch Dec 14 '20 at 16:18
  • 3
    Obvious way would be to `throw` an exception. Is that not acceptable for some reason? – Fred Larson Dec 14 '20 at 16:18
  • 1
    @EtiennedeMartel see https://stackoverflow.com/questions/1788923/parameter-vs-argument – Alan Birtles Dec 14 '20 at 16:19
  • 1
    @EtiennedeMartel That is wrong and sometimes it leads to misunderstandings. – Daniel Langr Dec 14 '20 at 16:19
  • 1
    You can use an assert, or static_assert (for compile time assertions) or as answered, throw an exception. – 0RR Dec 14 '20 at 16:22
  • 1
    @DanielLangr "Wrong" is too strong I think. There is another naming scheme where "parameters" are called "formal parameters" and "arguments" are called "actual parameters". Thus "parameters" can mean either (formal) parameters or arguments depending on which convention is followed. It is an inherently ambiguous word whose meaning depends on the chosen convention. – eerorika Dec 14 '20 at 16:24
  • 1
    @eerorika All right, I agree. I was referring to the C++ terminology posed by the C++ Standard (where this distinction is clear), but you're right that, generally, these terms may be used in a different way. – Daniel Langr Dec 14 '20 at 16:25
  • 1
    "parameter" and "argument" are often used interchangeably, but there is a subtle difference. An *argument* is what the caller passes in to a function. A *parameter* is the variable name local to the function. An analogy is an *argument* is like an *automobile*, and a *parameter* is like a *parking space*. – Eljay Dec 14 '20 at 16:27
  • is there no compiler warning/error if he supplies -1 to an argument of type unsigned int? – BitTickler Dec 14 '20 at 16:28
  • Thank you all for helping with my question. So, in my case, the more correct words for "number" and "degree" are _arguments_, not "parameters". I got that. eerorika gave me an answer that worked. However, I am curious: since `throw` terminates the execution of the program, that means that all code after the function is not compiled as well. Is it possible to "skip" the execution of the Power() function and compile the code that's after it? Also, if I was working in an IDE, e.g. Visual Studio, can I make the IDE display an error message before I even run the code? – Kotaka Danski Dec 14 '20 at 16:35
  • 1
    The way to "return nothing" for unacceptable parameters is either to `return 0;` (silently ignore problem) or have the return type be `std::optional` (more work on the caller side), or to throw an exception (more work somewhere up the call chain). A compiler would have a tough time detecting the general problem at compile time (but might be able to do so in this case, using a `constexpr` function and a `static_assert`). – Eljay Dec 14 '20 at 16:35
  • 1
    There are still more options to look into @Eljay Template function and template specialization. Also you could try to avoid that -1 is silently and implicitly cast to an unsigned degree value (which yields a segfault eventually). For example by writing your own number type. – BitTickler Dec 14 '20 at 16:38
  • in the general case it isn't possible to catch this at compile time, if you only care about being passed negative literals you might be able to find this with constexpr but that won't stop someone passing a variable containing a negative number to your function – Alan Birtles Dec 14 '20 at 17:53

2 Answers2

7

There is an alternative to returning a value: Throw a value. A typical example:

if(degree<0){
    throw std::invalid_argument("degree may not be negative!");
}

I want the compiler to refuse to compilate the code

In general, arguments are unknown until runtime, so this is not typically possible.

Your answer does the job for menicely. But I am curious: 'throw' terminates the program and prevents anything after Power() to be executed.

If you catch the thrown object, then you can continue immediately after the function from which the object was thrown.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Your answer does the job for menicely. But I am curious: 'throw' terminates the program and prevents anything after Power() to be executed. If the case was simply to '"skip" the function, so that the code after it is compiled, how would that be accomplished? – Kotaka Danski Dec 14 '20 at 16:27
  • @eerorika Thank you! – Kotaka Danski Dec 14 '20 at 16:36
1

The mere fact, that C++ does implicit type conversions, leaves you no way out of the predicament, that if you write unsigned int x = -1;, no matter which warnings you turn on with your compiler, you won't see any problem with that.

The only rule coming to mind, which might help you with that, is the notorious "max zero or one implicit conversions" rule. But I doubt it can be exploited in this case. (-1 would need to be converted to unsigned int, then to another type, implicitly). But I think from what I read on the page I linked above, numeric implicit conversions do not really count under some circumstances.

This leaves you but one other, also imperfect option. In the code below, I outline the basic idea. But there is endless room to refine the idea (more on that, later). This option is to resort to optional types in combination with your own integer type. The code below also only hints to what is possible. All that could be done in some fancy monadic framework or whatnot...

Obviously, in the code, posted in the question, it is a bad idea to have argument degree as an unsigned int, because then, a negative value gets implicitly converted and the function cannot protect itself from the hostile degree 0xFFFFFFFF (max value of unsigned int). If it wanted to check, it had better chosen int. Then it could check for negative values.

The code in the question also calls for a stack overflow, given it does not implement power in a tail recursive way. But this is just an aside and not subject to the question at hand. Let's get that one quickly out of the way.

// This version at least has a chance to benefit from tail call optimizations.

int internalPower_1 (int acc, int number, int degree) {
  if (1 == degree)
    return acc * number;

  return internalPower_1(acc*number, number, degree - 1);
}

int Power_1 (int number, int degree) {
  if (degree < 0)
    throw std::invalid_argument("degree < 0");

  return internalPower_1( 1, number, degree);
}

Now, would it not be nice if we could have integer types, which depended on the valid value range? Other languages have it (e.g. Common Lisp). Unless there is already something in boost (I did not check), we have to roll such a thing ourselves.

Code first, excuses later:

#include <iostream>
#include <stdexcept>
#include <limits>
#include <optional>
#include <string>

template <int MINVAL= std::numeric_limits<int>::min(),
      int MAXVAL = std::numeric_limits<int>::max()>
struct Integer
{
  int value;
  static constexpr int MinValue() {
    return MINVAL; }
  static constexpr int MaxValue() {
    return MAXVAL; }
  using Class_t = Integer<MINVAL,MAXVAL>;
  using Maybe_t = std::optional<Class_t>;

  // Values passed in during run time get handled
  // and punished at run time.
  // No way to work around this, because we are
  // feeding our thing of beauty from the nasty
  // outside world.
  explicit Integer (int v)
    : value{v}
  {
    if (v < MINVAL || v > MAXVAL)
      throw std::invalid_argument("Value out of range.");
  }

  static Maybe_t Init (int v) {
    if (v < MINVAL || v > MAXVAL) {
      return std::nullopt;
    }
    return Maybe_t(v);
  }
};

using UInt = Integer<0>;
using Int = Integer<>;

std::ostream& operator<< (std::ostream& os,
              const typename Int::Maybe_t & v) {
  if (v) {
    os << v->value;
  } else {
    os << std::string("NIL");
  }
  return os;
}


template <class T>
auto  operator* (const T& x,
         const T& y)
  -> T {
  if (x && y)
    return T::value_type::Init(x->value * y->value);
  return std::nullopt;
}


Int::Maybe_t internalPower_3 (const Int::Maybe_t& acc,
                  const Int::Maybe_t& number,
                  const UInt::Maybe_t& degree) {
  if (!acc) return std::nullopt;
  if (!degree) return std::nullopt;
  if (1 == degree->value) {
    return Int::Init(acc->value * number->value);
  }
  return internalPower_3(acc * number,
             number,
             UInt::Init(degree->value - 1));
}

Int::Maybe_t Power_3 (const Int::Maybe_t& number,
              const UInt::Maybe_t& degree) {
  if (!number) return std::nullopt;
  return internalPower_3 (Int::Init(1),
              number,
              degree);
}
              

int main (int argc, const char* argv[]) {
  std::cout << Power_1 (2, 3) << std::endl;
  std::cout << Power_3 (Int::Init(2),
            UInt::Init(3)) << std::endl;
  std::cout << Power_3 (Int::Init(2),
            UInt::Init(-2)) << std::endl;
  std::cout << "UInt min value = "
        << UInt::MinValue() << std::endl
        << "Uint max value = "
        << UInt::MaxValue() << std::endl;
  return 0;
}

The key here is, that the function Int::Init() returns Int::Maybe_t. Thus, before the error can propagate, the user gets a std::nullopt very early, if they try to init with a value which is out of range. Using the constructor of Int, instead would result in an exception.

In order for the code to be able to check, both signed and unsigned instances of the template (e.g. Integer<-10,10> or Integer<0,20>) use a signed int as storage, thus being able to check for invalid values, sneaking in via implicit type conversions. At the expense, that our unsigned on a 32 bit system would be only 31 bit...

What this code does not show, but which could be nice, is the idea, that the resulting type of an operation with two (different instances of) Integers, could be yet another different instance of Integer. Example: auto x = Integer<0,5>::Init(3) - Integer<0,5>::Init(5) In our current implementation, this would result in a nullopt, preserving the type Integer<0,5>. In a maybe better world, though it would as well be possible, that the result would be an Integer<-2,5>.

Anyway, as it is, some might find my little Integer<,> experiment interesting. After all, using types to be more expressive is good, right? If you write a function like typename Integer<-10,0>::Maybe_t foo(Integer<0,5>::Maybe_t x) is quite self explaining as to which range of values are valid for x.

BitTickler
  • 10,905
  • 5
  • 32
  • 53