-2

Prelude: I have updated this question to address NicolBolas' suggestion. Please leave a constructive comment if you downvote, thank you.

I'm writing a function that takes a number (uint) or a flag that it's not a number. It then goes on to work with the given number or -1.

Here's the idea

struct Numeric {
    virtual int to_int() const = 0;
};
struct NotANumber : Numeric {
    int to_int () const {
        return -1;
    }
}
struct Number : Numeric {
    const uint n;
    Number (const uint n): n(n) {}
    int to_int () const {
        return n;
    }
};

So then my function could be used as:

calculate_stuff(NotANumber());
calculate_stuff(Number(4));

My question is whether it's possible to move the to_int method outside of the classes, into the form of an overloaded function:

struct Numeric {};

struct NotANumber : Numeric {}

struct Number : Numeric {
    const uint n;
    Number (const uint n): n(n) {}
};

int Numeric_to_int (const NotANumber) {
    return -1;
}

int Numeric_to_int (const Number n) {
    return n.n;
}

This doesn't seem to work. Is it possible to fix this? Is the issue that overloading happens at compile time, whereas overriding is at runtime?

(I think I'm going to use std::optional and value_or for this specific problem, but I would still like to know the answer to my questions above)

Edit: sorry, I should've clarified what "doesn't work" means:

int calculate_stuff (const Numeric n) {
    std::cout << Numeric_to_int(n) << std::endl;
    return 0;
}

This doesn't compile: no matching function for call to ‘Numeric_to_int(const Numeric&)’

user357269
  • 1,835
  • 14
  • 40

3 Answers3

1

My question is whether it's possible to move the to_int method outside of the classes, into the form of an overloaded function.

Using overloaded non-member functions is a viable option only if you deal with the derived types at all times.

If you need the functionality through a reference or pointer to the base class type, then using overloaded non-member functions is not a viable option. In that case, a virtual member function is the right choice.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
1
int calculate_stuff (const Numeric n)

How could it work? You passed in a base class. By value. The compiler cannot know what derived class it used to be. In fact, since you took the base class by value, the compiler knows that n is neither of the derived classes.

C++ isn't Java; if you pass something by value, you make a copy (or move to it). And if you passed one of the derived classes, that causes slicing; you only copy the base class data. The parameter is therefore exactly what it says: an object who's dynamic type is Numeric. Exactly and only that, not one of its derived classes.

Even if you took a const Numeric &n instead, that won't help. The compiler doesn't know what type it used to be. And you can't even use dynamic_cast to convert it back, since Numeric is not a polymorphic class.

A better way to handle this is to actually use a proper variant class (optional is effectively a specialized variant).

Community
  • 1
  • 1
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Thanks for pointing out slicing, I hadn't heard of it before. I tried passing by value/reference, but neither worked. It's also helpful to know that sum types are called `variant`s in C++ – user357269 Aug 10 '16 at 18:23
0

You can use a template class and an using declaration to do that:

template<int v>
struct Numeric {};

using NotANumber = Numeric<-1>;

template<int N>
constexpr int Numeric_to_int (Numeric<N>) {
    return N;
}

int main() {
    static_assert(Numeric_to_int(NotANumber{}) == -1, "!");
    static_assert(Numeric_to_int(Numeric<42>{}) == 42, "!");
}

Also, consider the use of a std::integral_constant for the same purpose.
As an example:

template<int v>
using Numeric = std::integral_constant<int, v>;
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • This is cool, but I think it requires me to know at compile time what my number is? – user357269 Aug 10 '16 at 17:43
  • @user357269 From the example, I guessed they were known. Otherwise saying that you can use your function as `calculate_stuff(Number(4));` wouldn't have sense. – skypjack Aug 10 '16 at 17:49
  • can you explain why that wouldn't make sense? I know at compile time whether it's a number (positive integer) or not a number. – user357269 Aug 10 '16 at 17:52
  • @user357269 I mean, if you write - _I can use it as `Numeric{4}`_, one can think that you know at compile time your constants. It's not a minimal, working example that resembles the real problem. That's all. Not a problem, don't worry, just to be clearer in your questions in the future. – skypjack Aug 10 '16 at 18:05