1

I have a templated class Bounded which have parameters:

  • fundamental type
  • Min value allowed
  • Max value allowed
  • policy (wrap, limit)

I can do it if the struct defining policy is defined outside the Bounded class itself. Is it possible to define such struct as policy inside the Bounded class to limit scope or is there other solution to achieve the same?

I have tried it:

#include <cmath>
#include <numbers>

template <typename T, T Min, T Max, template<typename, T, T> class OverflowPolicy>
class Bounded : public OverflowPolicy<T, Min, Max>
{
    T m_value;
public:
    struct Limit
    {
        constexpr auto rangeReduction (T value) -> T
        {
            T result = std::fmod(value - Min, Max - Min) + Min;
            if (result < Min)
                result += Min;
            return result;
        }
    };
    struct Wrap
    {
        constexpr auto rangeReduction (T value)-> T
        {
            return (value < Min) ? Min : (value > Max) ? Max : value;
        }
    };
    constexpr Bounded (T value) : m_value (OverflowPolicy<T, Min, Max>::rangeReduction(value)){}
    constexpr explicit operator T () const {return m_value;}
};

constexpr auto tau = 2 * std::numbers::pi_v<float>;
using Radians = Bounded<float, 0.f, tau, Bounded::Wrap<float, 0.f, tau>;

auto main () -> int
{
    return static_cast<float>(Radians(10);
}

This gives an excpected error as the 4th template argument is Bounded class itself.

<source>:31:55: error: template argument 4 is invalid
   31 | using Radians = Bounded<float, 0.f, tau, Bounded::Wrap>;                                                                  
11
  • The policy is supposed to be a class template with 3 template arguments. But Wrap doesnt have any. Also if you moved Wrap outside the template, it wouldnt work. Commented May 12 at 16:13
  • This works, I have tried it. But is there any way to encapsulate it inside the templated class it itsefl? Bounded::Wrap<float, 0.f, tau> does not work either. Commented May 12 at 16:16
  • 1
    sorry my cat was sitting on the keys :D Commented May 12 at 16:20
  • 1
    You shouldn't repeat the type or the Min and Max values either. using Radians = Bounded<float, 0.f, tau, Wrap>; should be enough if you lift it out of the class. example Commented May 12 at 16:24
  • 2
    being able to define the policy outside of Bounded is the motivation to use a policy in the first place. Hence, its not clear what you are trying to achieve. I could imagine that you want to have it as default, but I am not sure. If you want Bounded to always use Bounded::Wrap there is no need to make it a template argument. Commented May 12 at 16:32

1 Answer 1

7

You cannot inherit from an inner class. This

struct bar;
struct foo : bar { struct bar{}; };

is a simpler example to demonstrate the same. You can only inherit from a complete class. bar is only complete when foo is defined.

Next, you confuse the (class) template with a class in several instances. For example Bounded::Wrap<float, 0.f, tau> makes no sense. Bounded is a template, it needs the template arguments before you can name its inner type. Wrap is not a template, it has no template arguments. To use it as policy for Bounded it has to be a template.

Anyhow there is no way to make this work directly as desired. Because even ignoring the fact that you cannot inherit from an incomplete type, the definition of Radians would still be recursive. You need a level of indirection, or just give up the idea to define Wrap inside Bounded.

You can define it inside some detail namespace and then use it as the default policy:

#include <cmath>
#include <cmath>
#include <numbers>

namespace detail {
template <typename T, T Min, T Max>
struct Wrap
{
    constexpr auto rangeReduction (T value)-> T
    {
        T result = std::fmod(value - Min, Max - Min) + Min;
        if (result < Min)
            result += Min;
        return result;
    }
};
template <typename T, T Min, T Max>
struct Limit
{
    constexpr auto rangeReduction (T value) -> T
    {
        return (value < Min) ? Min : (value > Max) ? Max : value;        
    }
};
}

template <typename T, T Min, T Max, template<typename, T, T> class OverflowPolicy = detail::Wrap>
class Bounded : public OverflowPolicy<T, Min, Max>
{
    T m_value;
public:
    constexpr Bounded (T value) : m_value (OverflowPolicy<T, Min, Max>::rangeReduction(value)){}
    constexpr explicit operator T () const {return m_value;}
};

constexpr auto tau = 2 * std::numbers::pi_v<float>;
using Radians = Bounded<float, 0.f, tau>;  // use the default policy

auto main () -> int
{
    return static_cast<float>(Radians(10)); // ) was missing
}

You also had the implementations of Wrap and Limit swapped.


If you merely want to limit or wrap a value, the code can be much simpler:

#include <cmath>
#include <cmath>
#include <numbers>

enum Overflow_behavior { wrap, limit};

template <typename T, T Min, T Max, Overflow_behavior overflow_behavior = wrap>
class Bounded 
{
    T m_value;
    auto rangeReduction(T value) {
        if constexpr (overflow_behavior == wrap) {
            T result = std::fmod(value - Min, Max - Min) + Min;
            if (result < Min)
                result += Min;
            return result;
        } else {
            return (value < Min) ? Min : (value > Max) ? Max : value;
        }
    }
public:
    constexpr Bounded (T value) : m_value (rangeReduction(value)){}
    constexpr explicit operator T () const {return m_value;}
};

constexpr auto tau = 2 * std::numbers::pi_v<float>;
using Radians = Bounded<float, 0.f, tau>;

int main ()
{
    return static_cast<int>(Radians(10));
}

Next I'd realize that the class Bounded merely modifies a T value but does nothing else. Requiring to call a constructor and a conversion operator is too contrived when a function can do the same (note the need for a static cast above):

#include <cmath>
#include <cmath>
#include <numbers>


enum Overflow_behavior { wrap, limit};

template <typename T, T Min, T Max, Overflow_behavior overflow_behavior = wrap>
auto bound(T value) {
    if constexpr (overflow_behavior == wrap) {
        T result = std::fmod(value - Min, Max - Min) + Min;
        if (result < Min)
            result += Min;
        return result;
    } else {
        return (value < Min) ? Min : (value > Max) ? Max : value;
    }    
}

constexpr auto tau = 2 * std::numbers::pi_v<float>;


int main ()
{
    return bound<float,0.f,tau>(10);
}

Note how now all is nicely encapsulated in one function (if you want you could even get rid of the enum by using a plain bool). I could go on. For limiting a value to a range there is already std::clamp and I suppose also wrapping around can still be simplified further.

Last but not least, your code gives the false impression that Radians would be a tagged type, but it isn't really. Note that

using Apples = Bounded<float, 0.f, tau>;

refers to the exact same type as Radians. If you want Apples and Radians to be distinct types (which is probably a good idea) you need to do more (which I consider as beyond the scope of the question ;).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.