2

This question demonstrates how to use C++20 concepts to choose overloads for a function template. I'm trying to do something analogous: choose specializations for a class template.

I'm starting with a class template for Angle<T> which wraps a floating point value containing an angle in radians. Using concepts, I can ensure that users don't instantiate Angle with anything other than a floating point type:

template <std::floating_point T> struct Angle { T m_radians; };

Later, I decided I'd like to let clients use a distinct implementation of Angle<T> that can handle integral types. In other words, I'd like to allow code like:

const auto theta = Angle<float>(3.14f);
const auto phi = Angle<int>(180);

So I tried adding a comparable template.

template <std::integral T> struct Angle { T m_degrees; };

The compilers view this additional implementation as a redeclaration of the template with a different constraint. I've tried several different ways of expressing my intent, but none satisfy any of the compilers I've tried. In fact, I cannot even find a way to do this with std::enable_if and traditional SFINAE--admittedly, it's entirely possible I don't understand SFINAE very well.

The only approach I've found requires making distinct specializations for each of the integral and floating point types.

template <std::floating_point T> struct AngleRad { T m_radians; };
template <std::integral T> struct AngleDeg { T m_degrees; };

template <typename T> struct Angle2 {};
template <> struct Angle2<float> : public AngleRad<float> {};
template <> struct Angle2<double> : public AngleRad<double> {};
template <> struct Angle2<long double> : public AngleRad<long double> {};
template <> struct Angle2<short> : public AngleDeg<short> {};
template <> struct Angle2<int> : public AngleDeg<int> {};
template <> struct Angle2<long> : public AngleDeg<long> {};
template <> struct Angle2<long long> : public AngleDeg<long long> {};
template <> struct Angle2<unsigned short> : public AngleDeg<unsigned short> {};
template <> struct Angle2<unsigned int> : public AngleDeg<unsigned int> {};
template <> struct Angle2<unsigned long> : public AngleDeg<unsigned long> {};
template <> struct Angle2<unsigned long long> : public AngleDeg<unsigned long long> {};

[Yes, I know there are a few more integral types. I'm just trying to illustrate the point. This example is contrived for simplicity, though it's inspired by actual code.]

Is there a way to use concepts to express this more simply?

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175

1 Answers1

6

You say

template<typename T> // requires (std::integral<T> || std::floating_point<T>) // optional
struct Angle;
template<std::integral T> struct Angle<T> { T m_degrees; };
template<std::floating_point T> struct Angle<T> { T m_radians; };

The template needs to be declared with a big enough domain to contain all of its specializations.

HTNW
  • 27,182
  • 1
  • 32
  • 60
  • Thanks! I was _very_ close almost had it on one of my first attempts. In fact, I had to do a diff to see what was different about my attempt and your answer. – Adrian McCarthy Jun 20 '21 at 18:37