оператор << () не разрешается из шаблона из-за наследования - PullRequest
0 голосов
/ 18 июня 2019

Я работаю с набором классов, и мой основной код выглядит следующим образом:

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;
}

Ожидаемый результат должен быть:

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

Вот мои уроки:

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;
}

Однако в main.cpp Visual Studio 2017 генерирует эту ошибку компилятора:

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>        ]

на основе этой строки кода:

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

и не может выполнитьl.lower()

Однако, если я делаю это в основном:

#include "calc.h"

int main() {
    neg_inf nif;
    pos_inf pif;

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

    return EXIT_SUCCESS;
}

Я получаю вывод:

-inf
inf

Это говорит мне, что мои operator<<()работая для унаследованных структур, однако, когда я передаю его родительский тип в качестве аргумента шаблона и передаю производные типы в конструктор моего limit класса, operator<<() не разрешаются.Кажется, это проблема двусмысленности, но я не уверен, как решить эту проблему.Что я здесь упускаю или пропускаю?


Как примечание, которое выходит за рамки этого вопроса, есть ли более элегантный способ изобразить -/+inf?Я использую здесь наследование, потому что + и - inf не числа, а скорее понятие, они похожи друг на друга, но указывают в разных направлениях.Поэтому, когда я передаю бесконечный тип в качестве аргумента шаблона, я хотел бы иметь возможность установить нижнюю границу на -inf, а верхнюю границу на + inf.Я хочу, чтобы связанный тип был шаблоном, потому что я мог бы использовать целочисленные или двойные границы, например, между [-1,1] или [0.0,1.0], в которых все это числовые границы.Я не уверен, как еще выразить бесконечность более изящным способом, и любые советы или предложения будут оценены.

Ответы [ 4 ]

2 голосов
/ 18 июня 2019

Не перегружайте операторы для подклассов таким образом.Используйте виртуальный метод для вывода и используйте универсальный тип с оператором перегрузки, который вызывает виртуальный метод:

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;
    }
};
2 голосов
/ 18 июня 2019

Итак, вы сделали перегрузки для operator<<, взяв const pos_inf& inf и const neg_inf& inf, но вы используете infinity в качестве типа шаблона, поэтому ваш lower() метод возвращает infinity.Конечно, перегрузки вашего оператора не будут использоваться, поскольку они являются производными типами от infinity.Почему бы просто не перегрузить operator<< для infinity?

Несколько быстрых идей, как решить эту проблему:

  1. Сделать виртуальный double operator()().Но вы не можете смешать это с constexpr.
  2. Используя template<typename dX, class lower_bound, class upper_bound> для limits класса, чтобы фактически указать типы для обеих границ, тогда ваши методы lower и upper могут вернуть pos_inf и neg_inf типов и ваши текущие операторы будут работать.Кроме того, для простоты вы также можете использовать второй тип по умолчанию для первого, если типы не всегда будут отличаться - template<typename dX, class lower_bound, class upper_bound = lower_bound>.
  3. После того, как вы расскажете больше о дизайне - почему бы тогда не сделать infinity класс шаблонный (так как я предполагаю, что вы хотите, чтобы он соответствовал dX, и устанавливали ли ограничения там?

    #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. Заставить lower/upper вернуть dX. Таким образом, выфактически оставьте разрешение от связанного типа до нужного вам типа значения внутри шаблона, и вы можете смешивать бесконечные и бесконечные пределы.

    #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;
    }
    
1 голос
/ 18 июня 2019

Я думаю, что вы слишком ограничиваете себя: вы можете отбросить базовый класс, добавить operator<< для обоих pos_inf и neg_inf и добавить дополнительный тип к limit, таким образом вы можете получить дваграницы разных типов.Вот что я имею в виду:

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;
}

Если этоя не прошу прощения.

0 голосов
/ 19 июня 2019

После получения отзывов от тех, кто оставил ответы, и от тех, кто оставил комментарии, и учитывая, что нижняя и верхняя границы могут не относиться к одному и тому же типу, я добавил в дополнительный параметр шаблона.В этой конкретной реализации это неизбежно.Тем не менее, я смог полностью исключить необходимость наследования и просто создал две разные структуры, по одной для каждого типа.Это также упростило мои operator<<() с.Поэтому мои занятия выглядят так:

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()();
}

Теперь в основном, очень похоже на мой оригинал, но с несколькими модификациями:

#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;
}

И это работает и дает мне вывод:

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

Примечание Однако, подумав об этом и заставив его работать и сравнив его с некоторыми другими ответами, которые ему соответствуют, с любопытством recurring thinkts пользователя ответ.

...