views:

217

answers:

7

I was bored and came up with such hack (pseudocode):

 1 struct proxy {
 2     operator int(); // int function
 3     operator double(); // double function
 4     proxy(arguments);
 5     arguments &arguments_;
 6 };
 7
 8 proxy function(arguments &args) {
 9     return proxy(args);
10 }
11 int v = function(...);
12 double u = function(...);

is it evil to use in real code?

my possible usage scenario is for example product of array elements, which may/may not overflow:

int size(short *array);
short size(short *array);

The reason for function, in case you use templates, than template parameters can be inferred from function arguments

+1  A: 

No, and it's not a hack. It's the whole point of operator overloading. As long as your overloads serve a purpose, then why not?

Duracell
+3  A: 

The issue is that if the function has two return types, it's probably doing two different (alternative) things . And to the extent possible, each function/method should do one coherent thing.

Matthew Flaschen
A: 

In your example, you allow casts to int and float. As long as these two casts perform the same basic logic, i.e. operator int() { return operator float(); } I see no problem. If they behave differently, this could definitely result in some surprises or ambiguities. This is because I would expect numeric results to have a coherent meaning.

Inverse
A: 

If you really mean something like this:

 1 struct proxy {
 2     operator long() { return refs.first; } // long has greater precision
 3     operator double() { return refs.second; } // double has greater range
 4     proxy( long const &, double const & );
 5     pair< long const &, double const & > refs;
 6 };
 7
 8 proxy function() {
 9     return proxy( numeric_limits<long>::max() + 1,
                     double( numeric_limits<long>::max() ) );
10 }
11 int v = function(...);
12 double u = function(...);

Then yes, I think that's cool and I would count it as a hack.

If it works. I didn't test it at all.

Potatoswatter
It looks like in your code, `function` does all the work, and then returns two possible results, which `proxy` then selects from based on the desired conversion operator. My impression of Aaa's code was that `function` would do nothing but forward the arguments to `proxy`, and the real work would occur in the conversion operators to generate just one result according to the desired type.
Rob Kennedy
well, I was thinking more about the lines of hiding actual functions behind cast operators. I think Rob just explained what I meant a second ago
aaa
@aaa: if `proxy` does the work, the name `proxy` is misleading. And that's the only hint you gave as to what is going on.
Potatoswatter
+1  A: 

The call of the function "function" became kind of context sensitive. I suppose, this trick can be exploited to support subject-oriented programming.

Subject-oriented programming is based on the observation that properties of an object are not inherent to the object itself, but depend on who perceive that object. For example, from the point of view of human, tree is not food, but from the point of view of termite, tree is food. Object-oriented paradigm doesn't support this observation directly, and people often come to complex unnatural designs, because they try to incorporate all different subjective views of an object into one entity ("class"), following thoughtlessly OOP guidelines.

So, let's try to state subjective perceptions explicitly, using the trick in question to get context sensitivity.

template<class FoodSource>
class FoodFrom {};
//forward declarations
class Tree;
class Termite;
class Human;

//property "food" of a tree
template<>
class FoodFrom<Tree>
{
public:
    FoodFrom(Tree& _tree): tree(_tree) {}

    //termite perception of tree as food
    operator FoodFor<Termite>()
    {
        int happiness_increase = 5;
        tree.mass -= 10;
        return FoodFor<Termite>(happiness_increase);
    }
    //human perception of tree as food
    operator FoodFor<Human>()
    {
        int happiness_increase = 0;
        return FoodFor<Human>(happiness_increase);
    }
private:
    Tree& tree;
};
//property "food" of a termite
template<>
class FoodFrom<Termite>
{
public:
    FoodFrom(Termite& _termite): termite(_termite) {}
    //human perception of termite as food
    operator FoodFor<Human>()
    {
        int happiness_increase = -100;
        //apparently, the termite ought to be terminated due to such a violent act
        termite.~Termite();
        return FoodFor<Human>(happiness_increase);
    }
private:
    Termite& termite;
};

//simple class FoodFor, just for demonstration purposes
class FoodBase
{
public:
    FoodBase(int _value) : value(_value) {}
    int value;
};
template<class T>
class FoodFor: public FoodBase
{
public:
    FoodFor(): FoodBase(0) {}
    FoodFor(int _value) : FoodBase(_value) {}
};

class AliveBeing
{
public:
    AliveBeing(): happiness(100) {}
    bool is_happy()
    {
        return happiness > 0;
    }
    void eat()
    {
        happiness += getMeal()->value;
    }
private:
    int happiness;
    virtual FoodBase* getMeal() = 0;
};
class Tree: public AliveBeing
{
public:
    FoodFrom<Tree> getFood(); //see definition below
    float mass;
    //...
private:
    //we don't call getMeal for a tree in this demo
    virtual FoodBase* getMeal() { return NULL; }
};

class Termite: public AliveBeing
{
public:
    FoodFrom<Termite> getFood(); //see definition below
    FoodFor<Termite> meal;
private:
    virtual FoodBase* getMeal() { return &meal; }
};

class Human: public AliveBeing
{
public:
    FoodFor<Human> meal;
private:
    virtual FoodBase* getMeal() { return &meal; }
};

//return proxy "FoodFrom" to "overload" return type
FoodFrom<Tree> Tree::getFood()
{ return FoodFrom<Tree>(*this); }
FoodFrom<Termite> Termite::getFood()
{ return FoodFrom<Termite>(*this); }

//usage
    Tree tree;
    Termite funny_bug;
    //funny_bug gets its perceived value of eating tree
    funny_bug.meal = tree.getFood();
    funny_bug.eat();
    if(funny_bug.is_happy())
        funny_bug.goFindThirdPlace();

    //...

    Human joel;
    //joel get its perceived value of eating tree
    joel.meal = tree.getFood();
    joel.eat();

    //...

    if(joel.see(funny_bug))
    {
        joel.meal = funny_bug.getFood();
        joel.eat();
    }
    if(joel.is_happy())
        joel.writeAnotherGreatArticle();

Note that the tree doesn't know what eats it.

(great question indeed, made me reflect upon it much)

Alsk
thanks. Interesting read
aaa
+1  A: 

I'd rather use template specialization, just feels less "hacky" and probably will be faster (no object creation, although of course that can be optimized away by smart compiler).

But anyway, I'd rather see code like

template<typename T> T function();

template<> int function() {
    return 1;
}

template<> float function() {
    return 1.0;
}

....
int a = function<int>();
float b = function<float>();

Nothing wrong with your code though, especially if you stay away from numeric types/pointers since otherwise unanticipated effects might occur, rules of conversion is quite complicated in C++.

vava
A: 

Actually it seems you have reinvented a Variant type. Just take a look at all *variant types that are present in many frameworks, eg.: MS uses VARIANT, Qt has QVariant.

Marcin Gil