-1

I'm working with a set of classes and my main code looks like this:

main.cpp

#include "calc.h"

int main() {
    neg_inf nif;
    pos_inf pif;

    limit<double, infinity> l( 3.4, nif, pif, 2.2 )

    std::cout << "value dx  = " << l.value() << '\n'
              << "lower lim = " << l.lower() << '\n'
              << "upper lim = " << l.upper() << '\n'
              << "step_size = " << l.step() << '\n';

    return EXIT_SUCCESS;
}

The expected output should be:

value dx  = 3.4
lower lim = -inf
upper lim = inf
step_size = 2.2

Here are my classes:

calc.h

#pragma once

#include <cmath>
#include <iostream>
#include <limits>
#include <type_traits> 

struct infinity {
protected:
    infinity() = default;
};

struct pos_inf : public infinity {
    constexpr double operator()() { return std::numeric_limits<double>::infinity(); }
};

struct neg_inf : public infinity {
   constexpr double operator()() { return -std::numeric_limits<double>::infinity(); }
};

std::ostream& operator<<( std::ostream& os, const pos_inf& inf );
std::ostream& operator<<( std::ostream& os, const neg_inf& inf );

template<typename dX, class bound>
class limit {
    dX dx;
    bound lowerBound;
    bound upperBound;
    double step_size;

public:
    limit( dX x, bound lower, bound upper, double step = 1 ) :
        dx{ x }, lowerBound{ lower }, upperBound { upper }, step_size { step }
    {}

    dX value() const { return dx; }
    bound lower() const { return lowerBound; }
    bound upper() const { return upperBound; }
    double step() const { return step_size; }
};

calc.cpp

#include "calc.h"

std::ostream& operator<<( std::ostream& os, const pos_inf& inf ) {
    // originally intended to do:
    // return os << inf(); // but fails to compile

    auto v = pos_inf()(); // this works
    return os << v;
}

std::ostream& operator<<( std::ostream& os, const neg_inf& inf ) {
    // same as above...

    auto v = neg_inf()();
    return os << v;
}

However in the main.cpp Visual Studio 2017 is generating this compiler error:

c:\***\main.cpp(33): error C2679: binary '<<': no operator found which takes a right-hand operand of type 'bound' (or there is no acceptable conversion)
1>        with
1>        [
1>            bound=infinity
1>        ]

based on this line of code:

<< "lower lim = " << l.lower() << '\n'

and is failing from l.lower()

However if I do this in main:

#include "calc.h"

int main() {
    neg_inf nif;
    pos_inf pif;

    std::cout << nif << '\n' << pif << '\n'

    return EXIT_SUCCESS;
}

I am getting the output:

-inf
inf

This tells me that my operator<<() are working for the inherited structs, however when I pass it's parent type as a template argument and pass the derived types into the constructor of my limit class, the operator<<() are not resolving. It appears to be an ambiguity problem but I'm not sure how to resolve this. What am I missing or overlooking here?


As a side note which is outside of this question, is there a more elegant way to represent -/+inf? I'm using inheritance here because + and - inf are not numbers but more of a concept, they are similar to each other but point in different directions. So when I pass an infinity type as a template argument I'd like to be able to set the lower bound to -inf and the upper bound to +inf. I want the bound type to be a template because I might want to use integer bounds or double bounds for example between [-1,1] or [0.0,1.0] in which these are all numeric bounds. I'm not sure how else to express infinity in a more elegant way and any tips or suggestions will be appreciated.

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59

4 Answers4

2

Well, you have made overloads for operator<< taking const pos_inf& inf and const neg_inf& inf, but you are using infinity as the template type, thus your lower() method returns infinity. Of course the your operator overloads will not be used since they are derived types from infinity. Why not just overload the operator<< for infinity ?

Some quick ideas how to solve this:

  1. Making the double operator()() virtual. But you can't mix that with constexpr.
  2. Using template<typename dX, class lower_bound, class upper_bound> for limits class to actually specify the types for both bounds, then your lower and upper methods can return the pos_inf and neg_inf types and your current operators will work. Also, for simplicity you can also default the second type fo the first if the types will not always differ - template<typename dX, class lower_bound, class upper_bound = lower_bound>.
  3. After giving more though about the design - why then not actually make the infinity class templated (since I assume you want it to match dX, and implement the limits there?

    #include <cmath>
    #include <iostream>
    #include <limits>
    #include <type_traits> 
    
    template<typename T>
    struct infinity {
    public:
        infinity() = default;
    
        constexpr double lower()
        {
            return -std::numeric_limits<T>::infinity();
        }
    
        constexpr double upper()
        {
            return std::numeric_limits<T>::infinity();
        }
    };
    
    
    template<typename dX>
    class limit {
        dX dx;
        double step_size;
    
    public:
        limit(dX x, double step = 1) :
            dx{ x }, step_size{ step }
        {}
    
        dX value() const { return dx; }
        dX lower() const { return infinity<dX>().lower(); }
        dX upper() const { return infinity<dX>().upper(); }
        double step() const { return step_size; }
    };
    
    
    int main() {
    
        limit<double> l(3.4, 2.2);
    
            std::cout << "value dx  = " << l.value() << '\n'
            << "lower lim = " << l.lower() << '\n'
            << "upper lim = " << l.upper() << '\n'
            << "step_size = " << l.step() << '\n';
    
        return EXIT_SUCCESS;
    }
    
  4. Making lower/upper return dX. That way you actually leave the resolution from the bound type to your needed value type inside the template, and you can mix infinite and non-infinite limits.

    #include <cmath>
    #include <iostream>
    #include <limits>
    #include <type_traits> 
    
    struct pos_inf {
        constexpr operator double() const { return std::numeric_limits<double>::infinity(); }
    };
    
    struct neg_inf {
        constexpr operator double() const { return -std::numeric_limits<double>::infinity(); }
    };
    
    
    template<typename dX, typename upper_bound = dX, typename lower_bound = dX>
    class limit {
        dX dx;
        upper_bound lowerBound;
        lower_bound upperBound;
        double step_size;
    
    public:
        limit(dX x, upper_bound lower, lower_bound upper, double step = 1) :
            dx{ x }, lowerBound{ lower }, upperBound{ upper }, step_size{ step }
        {}
    
        // with infinity these two will invoke operator double(), with actual double it will return the fixed value
        dX lower() const { return lowerBound; } 
        dX upper() const { return upperBound; }
        dX value() const { return dx; }
        double step() const { return step_size; }
    };
    
    int main() {
    
        limit<double, pos_inf, neg_inf> l(3.4, pos_inf(), neg_inf(), 2.2); // infinity
        limit<double> l2(3.4, 1, 5, 2.2); // fixed values
            std::cout << "value dx  = " << l.value() << '\n'
            << "lower lim = " << l.lower() << '\n'
            << "upper lim = " << l.upper() << '\n'
            << "step_size = " << l.step() << '\n';
        return EXIT_SUCCESS;
    }
    
Rudolfs Bundulis
  • 11,636
  • 6
  • 33
  • 71
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/195160/discussion-on-answer-by-rudolfs-bundulis-operator-not-resolving-from-templat). – Samuel Liew Jun 19 '19 at 00:07
2

Do not overload operators for the subclasses that way. Use a virtual method to do the output and use the generic type with the overload operator that calls the virtual method:

class infinity {
  public:
    virtual ostream &printTo(ostream &o) const = 0;
};
ostream &operator<<(ostream &o,const infinity &i) {
  return i.printTo(o);
}
class neg_inf : public infinity {
  public:
    virtual ostream &printTo(ostream &o) const {
        // do what you want
        return o;
    }
};
Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
1

I think you are constraining yourself too much: you can drop the base class, add operator<< for both pos_inf and neg_inf and add an extra type to limit, in this way you can have the two bounds of different types. Here is what I mean:

Calc.h

#pragma once

#include <cmath>
#include <iostream>
#include <limits>
#include <type_traits> 


struct pos_inf {
    constexpr double operator()() const { return std::numeric_limits<double>::infinity(); }
};

struct neg_inf  {
    constexpr double operator()() const { return -std::numeric_limits<double>::infinity(); }
};
// Both operators defined
std::ostream& operator<<(std::ostream& os, const pos_inf& inf);
std::ostream& operator<<(std::ostream& os, const neg_inf& inf);

//extra template type  in limit
template<typename dX, class lowerBoundType, class UpperBoundType>
class limit {
    dX dx;
    lowerBoundType lowerBound;
    UpperBoundType upperBound;
    double step_size;

public:
    limit(dX x, lowerBoundType lower, UpperBoundType upper, double step = 1) :
        dx{ x }, lowerBound{ lower }, upperBound{ upper }, step_size{ step }
    {}

    dX value() const { return dx; }
    lowerBoundType lower() const { return lowerBound; }
    UpperBoundType upper() const { return upperBound; }
    double step() const { return step_size; }
};

Calc.cpp

#include "calc.h"

std::ostream& operator<<(std::ostream& os, const pos_inf& inf) {
    return os << inf(); // but fails to compile

}

std::ostream& operator<<(std::ostream& os, const neg_inf& inf) {
    return os << inf(); // but fails to compile

}

main.cpp

#include "calc.h"

int main() {
    neg_inf nif;
    pos_inf pif;

    limit<double, neg_inf, pos_inf> l(3.4, nif, pif, 2.2);

        std::cout << "value dx  = " << l.value() << '\n';
        std::cout << "lower lim = " << l.lower() << '\n';
        std::cout << "upper lim = " << l.upper() << '\n';
        std::cout << "step_size = " << l.step() << '\n';

    return EXIT_SUCCESS;
}

If this is not what you wanted, I apologize.

  • I like this and I'll give it some thought, but I was trying to avoid having multiple template arguments if possible. Another words the less the better; this is more for a readability thing when instantiating the templates. – Francis Cugler Jun 18 '19 at 20:23
  • 1
    Unfortunately virtual and constexpr are, as of now, not really compatible with each other, probably the situation will be better in the future. https://stackoverflow.com/questions/34828161/can-virtual-functions-be-constexpr – CuriouslyRecurringThoughts Jun 18 '19 at 20:26
  • I had thought about it for some time and took some consideration into the concept that the `lower` and `upper` bounds may be different types such as `[1, +inf]` and this lead me to add in the 2nd template parameter for the bounds. Then after doing that I dawned one me to try your approach where `neg_inf` and `pos_inf` are two separate function objects that return a constexpr. And when I pieced them together, everything worked. – Francis Cugler Jun 18 '19 at 21:30
  • 1
    @FrancisCugler I think we can do it even simpler. Why not having simply a class bound that contains inside the numerical bound? In this way you can do bound lower{-5}, bound upper{+5} . For +/- infinity you can simply use bound and set the numeric limit. You can hide it creating make functions like make_lower_inf, make_upper_inf. So yeah, plenty of options. – CuriouslyRecurringThoughts Jun 18 '19 at 21:35
  • The only difference with my answer from your answer is within the `operator<<()`s; instead of using the `parameter` that is passed in because that fails to compile I simply did this instead: `return os << neg_inf()();` and `return os << pos_inf()();` instead and this works. – Francis Cugler Jun 18 '19 at 21:35
  • Yeah, but when I start making some of the functions that will `apply` the limit. I am planning on specializing this class for when any of the bound types are of an `infinity` type, and leave the general template when they are any `numerical` type. – Francis Cugler Jun 18 '19 at 21:36
0

After taking the feedback from some who left answers and others who left comments and taking into consideration that the lower and upper bounds may not be of the same type, I had added in the extra template parameter. In this particular implementation it is unavoidable. However by doing so I was able to eliminate the need for inheritance altogether and just made two different structures one for each type. This also simplified my operator<<()s. So my classes now look like this:

calc.h

#pragma once

#include <cmath>
#include <iostream>
#include <limits>
#include <type_traits>

struct neg_inf {
    constexpr double operator()() { return -std::numeric_limits<double>::infinity(); }
};

struct pos_inf {
    constexpr double operator()() { return std::numeric_limits<double>::infinity(); }
};

template<typename dX, class LowerBound, class UpperBound>
class limit {
    dX dx;
    LowerBound lowerBound;
    UpperBound upperBound;
    double step_size;

public:
    limit( dX x, LowerBound lower, UpperBound upper, double step = 1 ) :
        dx{ x }, lowerBound{ lower }, upperBound { upper }, step_size{ step }
    {}

    dX value() const { return dx; }
    LowerBound lower() const { return lowerBound; }
    UpperBound upper() const { return upperBound; }
    double step() const { return step_size; }
};

calc.cpp

#include "calc.h"

std::ostream& operator<<(std::ostream& os, const neg_inf& inf) {
    // not using the parameter, using constructor and its operator()
    // since this is a function object or functor and returns a constexpr
    return os << neg_inf()();
}

std::ostream& operator<<(std::ostream& os, const pos_inf& inf) {
    // not using the parameter, using constructor and its operator()
    // since this is a function object or functor and returns a constexpr
    return os << pos_inf()();
}

Now in main, very similar as in my original but with the few modifications:

#include "calc.h"

int main() { 
    neg_inf nif;
    pos_inf pif;

    limit<double, neg_inf, pos_inf> l(3.4, nif, pif, 2.2);

    std::cout << "value dx  = " << l.value() << '\n'
              << "lower lim = " << l.lower() << '\n'
              << "upper lim = " << l.upper() << '\n'
              << "step_size = " << l.step() << '\n';

    return EXIT_SUCCESS;
}

And this does work and gives me the output:

value dx  = 3.4
lower lim = -inf
upper lim = inf
step_size = 2.2

Note However after thinking about this and getting it to work and comparing it with some of the other answers this does match, user's curiouslyrecurringthoughts answer.

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59