11
\$\begingroup\$

In C++, primitive types are treated differently than user-defined types. If you do not initialize them, their values are undefined in some cases. This is an easy mistake to make.

I wanted to make a class that wraps primitive types, etc. so that the default constructor is always called without adding any other run-time overhead or placing any restrictions on the type. I want to expose any operations available to that primitive type, with a few limitations:

  1. I want to prevent implicit conversions that may result in a loss of information (with support to use static_cast when needed) and allow any that do not.

  2. I want to place restrictions on char. It should be for building strings and not arithmetic. In cases where char is a typedef, I don't think I have any control.

  3. I also want to restrict converting from unsigned to signed types.

  4. I don't want to allow mixing booleans with arithmetic. Numbers are converted to bools by doing comparisons or using the ! operator.

I am calling this wrapper primitive, but I'm up for a better name. I did some minor template metaprogramming to only enable primitive for arithmetic types (bool, char types, integral types and floating-point types). This should cover all the fundamental types that can initialized, except pointers (smart pointers cover this case). I also am trying to turn off bit-wise operations for floating-point numbers and booleans.

You'll note I am using constexpr to do as much at compile time as possible. I use noexcept as much as I can, too. Please, let me know if I've missed something or overstepped anywhere.

Someone wanting to use this class might define using Int = primitive<int>;. My unit tests are able to static_assert to test most operations, except self-assignment and iostream operators.

#ifndef PRIMITIVE_HPP
#define PRIMITIVE_HPP

#include <type_traits>
#include <iosfwd>

template <typename T, typename = std::enable_if_t< std::is_arithmetic<T>::value >>
class primitive final {
    T m_value;

public:
    using value_type = T;

    constexpr primitive() noexcept: m_value() {}
    constexpr primitive(T const& value) noexcept: m_value(value) {}

    primitive(primitive const&) noexcept = default;
    primitive(primitive &&) noexcept = default;

    primitive& operator=(primitive const&) noexcept = default;
    primitive& operator=(primitive &&) noexcept = default;

    constexpr T const& get() const noexcept { return m_value; }

    template <typename U = T, typename = std::enable_if_t<!std::is_same<U, bool>::value  >>
    constexpr primitive const& operator+() const noexcept { return *this; }
    template <typename U = T, typename = std::enable_if_t<!std::is_same<U, bool>::value  >>
    constexpr primitive operator-() const noexcept { return -this->get(); }

    template <typename U = T, typename = std::enable_if_t< std::is_integral<U>::value && !std::is_same<U, bool>::value >>
    constexpr primitive operator~() const noexcept { return ~this->get(); }

    constexpr primitive<bool> operator!() const noexcept { return !this->get(); }

    primitive& operator++() noexcept { 
        ++m_value; 
        return *this; 
    }
    primitive operator++(int) noexcept { 
        return m_value++;
    }

    primitive& operator--() noexcept { 
        --m_value; 
        return *this; 
    }
    primitive operator--(int) noexcept { 
        return m_value--;
    }

    template <typename U>
    primitive& operator+=(U const& other) noexcept {
        m_value += other;
        return *this;
    }
    template <typename U>
    primitive& operator+=(primitive<U> const& other) noexcept {
        m_value += other.get();
        return *this;
    }

    template <typename U>
    primitive& operator-=(U const& other) noexcept {
        m_value -= other;
        return *this;
    }
    template <typename U>
    primitive& operator-=(primitive<U> const& other) noexcept {
        m_value -= other.get();
        return *this;
    }

    template <typename U>
    primitive& operator*=(U const& other) noexcept {
        m_value *= other;
        return *this;
    }
    template <typename U>
    primitive& operator*=(primitive<U> const& other) noexcept {
        m_value *= other.get();
        return *this;
    }

    template <typename U>
    primitive& operator/=(U const& other) noexcept {
        m_value /= other;
        return *this;
    }
    template <typename U>
    primitive& operator/=(primitive<U> const& other) noexcept {
        m_value /= other.get();
        return *this;
    }

    template <typename U, typename = std::enable_if_t< std::is_integral<T>::value && std::is_integral<U>::value >>
    primitive& operator%=(U const& other) noexcept {
        m_value %= other;
        return *this;
    }
    template <typename U, typename = std::enable_if_t< std::is_integral<T>::value && std::is_integral<U>::value >>
    primitive& operator%=(primitive<U> const& other) noexcept {
        m_value %= other.get();
        return *this;
    }

    template <typename U, typename = std::enable_if_t< std::is_integral<T>::value && std::is_integral<U>::value >>
    primitive& operator<<=(U const& other) noexcept {
        m_value <<= other;
        return *this;
    }
    template <typename U, typename = std::enable_if_t< std::is_integral<T>::value && std::is_integral<U>::value >>
    primitive& operator<<=(primitive<U> const& other) noexcept {
        m_value <<= other.get();
        return *this;
    }

    template <typename U, typename = std::enable_if_t< std::is_integral<T>::value && std::is_integral<U>::value >>
    primitive& operator>>=(U const& other) noexcept {
        m_value >>= other;
        return *this;
    }
    template <typename U, typename = std::enable_if_t< std::is_integral<T>::value && std::is_integral<U>::value >>
    primitive& operator>>=(primitive<U> const& other) noexcept {
        m_value >>= other.get();
        return *this;
    }

    template <typename U, typename = std::enable_if_t< std::is_integral<T>::value && std::is_integral<U>::value >>
    primitive& operator&=(U const& other) noexcept {
        m_value &= other;
        return *this;
    }
    template <typename U, typename = std::enable_if_t< std::is_integral<T>::value && std::is_integral<U>::value >>
    primitive& operator&=(primitive<U> const& other) noexcept {
        m_value &= other.get();
        return *this;
    }

    template <typename U, typename = std::enable_if_t< std::is_integral<T>::value && std::is_integral<U>::value >>
    primitive& operator|=(U const& other) noexcept {
        m_value |= other;
        return *this;
    }
    template <typename U, typename = std::enable_if_t< std::is_integral<T>::value && std::is_integral<U>::value >>
    primitive& operator|=(primitive<U> const& other) noexcept {
        m_value |= other.get();
        return *this;
    }

    template <typename U, typename = std::enable_if_t< std::is_integral<T>::value && std::is_integral<U>::value >>
    primitive& operator^=(U const& other) noexcept {
        m_value ^= other;
        return *this;
    }
    template <typename U, typename = std::enable_if_t< std::is_integral<T>::value && std::is_integral<U>::value >>
    primitive& operator^=(primitive<U> const& other) noexcept {
        m_value ^= other.get();
        return *this;
    }

    template <typename U = T, typename = std::enable_if_t< std::is_same<U, signed char>::value >>
    constexpr operator primitive<short>() const noexcept { return m_value; }

    template <typename U = T, typename = std::enable_if_t< std::is_same<U, unsigned char>::value >>
    constexpr operator primitive<unsigned short>() const noexcept { return m_value; }

    template <typename U = T, typename = std::enable_if_t< std::is_same<U, signed char>::value || std::is_same<U, short>::value >>
    constexpr operator primitive<int>() const noexcept { return m_value; }

    template <typename U = T, typename = std::enable_if_t< std::is_same<U, unsigned char>::value || std::is_same<U, unsigned short>::value >>
    constexpr operator primitive<unsigned int>() const noexcept { return m_value; }

    template <typename U = T, typename = std::enable_if_t< 
        std::is_same<U, signed char>::value
        || std::is_same<U, short>::value 
        || std::is_same<U, int>::value
    >>
    constexpr operator primitive<long>() const noexcept { return m_value; }

    template <typename U = T, typename = std::enable_if_t< 
        std::is_same<U, unsigned char>::value 
        || std::is_same<U, unsigned short>::value
        || std::is_same<U, unsigned int>::value
    >>
    constexpr operator primitive<unsigned long>() const noexcept { return m_value; }

    template <typename U = T, typename = std::enable_if_t< 
        std::is_same<U, signed char>::value
        || std::is_same<U, short>::value 
        || std::is_same<U, int>::value
        || std::is_same<U, long>::value
    >>
    constexpr operator primitive<long long>() const noexcept { return m_value; }

    template <typename U = T, typename = std::enable_if_t< 
        std::is_same<U, unsigned char>::value 
        || std::is_same<U, unsigned short>::value
        || std::is_same<U, unsigned int>::value
        || std::is_same<U, unsigned long>::value
    >>
    constexpr operator primitive<unsigned long long>() const noexcept { return m_value; }

    template <typename U = T, typename = std::enable_if_t<
        std::is_same<U, signed char>::value 
        || std::is_same<U, unsigned char>::value
        || std::is_same<U, short>::value 
        || std::is_same<U, unsigned short>::value
        || std::is_same<U, int>::value
        || std::is_same<U, unsigned int>::value
        || std::is_same<U, long>::value
        || std::is_same<U, unsigned long>::value
        || std::is_same<U, float>::value
    >>
    constexpr operator primitive<double>() const noexcept { return m_value; }

    template <typename U = T, typename = std::enable_if_t<
        std::is_same<U, signed char>::value 
        || std::is_same<U, unsigned char>::value
        || std::is_same<U, short>::value 
        || std::is_same<U, unsigned short>::value
        || std::is_same<U, int>::value
        || std::is_same<U, unsigned int>::value
        || std::is_same<U, long>::value
        || std::is_same<U, unsigned long>::value
        || std::is_same<U, float>::value
        || std::is_same<U, double>::value
    >>
    constexpr operator primitive<long double>() const noexcept { return m_value; }

    template <typename U>
    constexpr explicit operator primitive<U>() const noexcept { return static_cast<U>(m_value); }

    friend std::istream& operator>>(std::istream& lhs, primitive<T> & rhs) { return lhs >> rhs.m_value; }
};

template <typename T>
constexpr primitive<T> operator+(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() + rhs; }
template <typename T>
constexpr primitive<T> operator+(T const& lhs, primitive<T> const& rhs) noexcept { return lhs + rhs.get(); }
template <typename T1, typename T2>
constexpr auto operator+(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept -> primitive<decltype(lhs.get() + rhs.get())> {
    return lhs.get() + rhs.get();
}

template <typename T>
constexpr primitive<T> operator-(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() - rhs; }
template <typename T>
constexpr primitive<T> operator-(T const& lhs, primitive<T> const& rhs) noexcept { return lhs - rhs.get(); }
template <typename T1, typename T2>
constexpr auto operator-(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept -> primitive<decltype(lhs.get() - rhs.get())> {
    return lhs.get() - rhs.get();
}

template <typename T>
constexpr primitive<T> operator*(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() * rhs; }
template <typename T>
constexpr primitive<T> operator*(T const& lhs, primitive<T> const& rhs) noexcept { return lhs * rhs.get(); }
template <typename T1, typename T2>
constexpr auto operator*(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept -> primitive<decltype(lhs.get() * rhs.get())> {
    return lhs.get() * rhs.get();
}

template <typename T>
constexpr primitive<T> operator/(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() / rhs; }
template <typename T>
constexpr primitive<T> operator/(T const& lhs, primitive<T> const& rhs) noexcept { return lhs / rhs.get(); }
template <typename T1, typename T2>
constexpr auto operator/(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept -> primitive<decltype(lhs.get() / rhs.get())> {
    return lhs.get() / rhs.get();
}

template <typename T, typename = std::enable_if_t< std::is_integral<T>::value >>
constexpr primitive<T> operator%(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() % rhs; }
template <typename T, typename = std::enable_if_t< std::is_integral<T>::value >>
constexpr primitive<T> operator%(T const& lhs, primitive<T> const& rhs) noexcept { return lhs % rhs.get(); }
template <typename T1, typename T2, typename = std::enable_if_t< std::is_integral<T1>::value && std::is_integral<T2>::value >>
constexpr auto operator%(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept -> primitive<decltype(lhs.get() % rhs.get())> {
    return lhs.get() % rhs.get();
}

template <typename T, typename = std::enable_if_t< std::is_integral<T>::value >>
constexpr primitive<T> operator&(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() & rhs; }
template <typename T, typename = std::enable_if_t< std::is_integral<T>::value >>
constexpr primitive<T> operator&(T const& lhs, primitive<T> const& rhs) noexcept { return lhs & rhs.get(); }
template <typename T1, typename T2, typename = std::enable_if_t< std::is_integral<T1>::value && std::is_integral<T2>::value >>
constexpr auto operator&(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept -> primitive<decltype(lhs.get() & rhs.get())> {
    return lhs.get() & rhs.get();
}

template <typename T, typename = std::enable_if_t< std::is_integral<T>::value >>
constexpr primitive<T> operator|(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() | rhs; }
template <typename T, typename = std::enable_if_t< std::is_integral<T>::value >>
constexpr primitive<T> operator|(T const& lhs, primitive<T> const& rhs) noexcept { return lhs | rhs.get(); }
template <typename T1, typename T2, typename = std::enable_if_t< std::is_integral<T1>::value && std::is_integral<T2>::value >>
constexpr auto operator|(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept -> primitive<decltype(lhs.get() | rhs.get())> {
    return lhs.get() | rhs.get();
}

template <typename T, typename = std::enable_if_t< std::is_integral<T>::value >>
constexpr primitive<T> operator^(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() ^ rhs; }
template <typename T, typename = std::enable_if_t< std::is_integral<T>::value >>
constexpr primitive<T> operator^(T const& lhs, primitive<T> const& rhs) noexcept { return lhs ^ rhs.get(); }
template <typename T1, typename T2, typename = std::enable_if_t< std::is_integral<T1>::value && std::is_integral<T2>::value >>
constexpr auto operator^(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept -> primitive<decltype(lhs.get() ^ rhs.get())>  {
    return lhs.get() ^ rhs.get();
}

template <typename T, typename = std::enable_if_t< std::is_integral<T>::value >>
constexpr primitive<T> operator<<(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() << rhs; }
template <typename T, typename = std::enable_if_t< std::is_integral<T>::value >>
constexpr primitive<T> operator<<(T const& lhs, primitive<T> const& rhs) noexcept { return lhs << rhs.get(); }
template <typename T1, typename T2, typename = std::enable_if_t< std::is_integral<T1>::value && std::is_integral<T2>::value >>
constexpr auto operator<<(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept -> primitive<decltype(lhs.get() << rhs.get())> {
    return lhs.get() << rhs.get();
}

template <typename T, typename = std::enable_if_t< std::is_integral<T>::value >>
constexpr primitive<T> operator>>(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() >> rhs; }
template <typename T, typename = std::enable_if_t< std::is_integral<T>::value >>
constexpr primitive<T> operator>>(T const& lhs, primitive<T> const& rhs) noexcept { return lhs >> rhs.get(); }
template <typename T1, typename T2, typename = std::enable_if_t< std::is_integral<T1>::value && std::is_integral<T2>::value >>
constexpr auto operator>>(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept -> primitive<decltype(lhs.get() >> rhs.get())> {
    return lhs.get() >> rhs.get();
}

constexpr primitive<bool> operator&&(primitive<bool> const& lhs, bool const& rhs) noexcept { return lhs.get() && rhs; }
constexpr primitive<bool> operator&&(bool const& lhs, primitive<bool> const& rhs) noexcept { return lhs && rhs.get(); }
constexpr primitive<bool> operator&&(primitive<bool> const& lhs, primitive<bool> const& rhs) noexcept {
    return lhs.get() && rhs.get();
}

constexpr primitive<bool> operator||(primitive<bool> const& lhs, bool const& rhs) noexcept { return lhs.get() || rhs; }
constexpr primitive<bool> operator||(bool const& lhs, primitive<bool> const& rhs) noexcept { return lhs || rhs.get(); }
constexpr primitive<bool> operator||(primitive<bool> const& lhs, primitive<bool> const& rhs) noexcept {
    return lhs.get() || rhs.get();
}

template <typename T>
constexpr bool operator==(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() == rhs; }
template <typename T>
constexpr bool operator==(T const& lhs, primitive<T> const& rhs) noexcept { return lhs == rhs.get(); }
template <typename T1, typename T2>
constexpr bool operator==(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept {
    return lhs.get() == rhs.get();
}

template <typename T>
constexpr bool operator!=(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() != rhs; }
template <typename T>
constexpr bool operator!=(T const& lhs, primitive<T> const& rhs) noexcept { return lhs != rhs.get(); }
template <typename T1, typename T2>
constexpr bool operator!=(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept {
    return lhs.get() != rhs.get();
}

template <typename T>
constexpr bool operator<(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() < rhs; }
template <typename T>
constexpr bool operator<(T const& lhs, primitive<T> const& rhs) noexcept { return lhs < rhs.get(); }
template <typename T1, typename T2>
constexpr bool operator<(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept {
    return lhs.get() < rhs.get();
}

template <typename T>
constexpr bool operator<=(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() <= rhs; }
template <typename T>
constexpr bool operator<=(T const& lhs, primitive<T> const& rhs) noexcept { return lhs <= rhs.get(); }
template <typename T1, typename T2>
constexpr bool operator<=(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept {
    return lhs.get() <= rhs.get();
}

template <typename T>
constexpr bool operator>(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() > rhs; }
template <typename T>
constexpr bool operator>(T const& lhs, primitive<T> const& rhs) noexcept { return lhs > rhs.get(); }
template <typename T1, typename T2>
constexpr bool operator>(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept {
    return lhs.get() > rhs.get();
}

template <typename T>
constexpr bool operator>=(primitive<T> const& lhs, T const& rhs) noexcept { return lhs.get() >= rhs; }
template <typename T>
constexpr bool operator>=(T const& lhs, primitive<T> const& rhs) noexcept { return lhs >= rhs.get(); }
template <typename T1, typename T2>
constexpr bool operator>=(primitive<T1> const& lhs, primitive<T2> const& rhs) noexcept {
    return lhs.get() >= rhs.get();
}

template <typename T>
std::ostream& operator<<(std::ostream& lhs, primitive<T> const& rhs) { return lhs << rhs.get(); }

#endif

In case anyone wants to see the latest code, I created a repo.

\$\endgroup\$
7
  • \$\begingroup\$ Since posting: I dropped some of the #includes, I marked the ctors and assignment operators noexcept, I simplified the postfix operators and I realized some of the template member functions weren't compiling on gcc. \$\endgroup\$ Sep 26, 2016 at 17:12
  • \$\begingroup\$ You can always edit your post as long as it does not invalidate existing answers. \$\endgroup\$ Sep 26, 2016 at 17:17
  • 1
    \$\begingroup\$ I have been editing as I go, just a recap in case anyone snagged a copy. \$\endgroup\$ Sep 26, 2016 at 17:19
  • 1
    \$\begingroup\$ I have rolled back the last edit. Please see what you may and may not do after receiving answers. \$\endgroup\$
    – Vogel612
    Sep 26, 2016 at 23:38
  • 1
    \$\begingroup\$ @PeterRader The destructor is implicitly declared and is not required for primitive types. In this specific case, the rule of 0/3/5 does not apply, as we're dealing with POD types, not a resource. Additionally, a user-defined constructor (not implicit, or not declared as = default) will make the class not trivially destructible. \$\endgroup\$ Sep 27, 2016 at 1:36

1 Answer 1

5
\$\begingroup\$

Remove redundant noexcept specifiers.

Defaulted special member functions allow the compiler to deduce whether a function is noexcept or not.

These:

primitive(primitive const&) noexcept = default;
primitive(primitive &&) noexcept = default;

Can be replaced with:

primitive(primitive const&) = default;
primitive(primitive &&) = default;

Behaviour is equivalent. This applies to your assignment operators as well.


Remove explicit calls to this.

The this pointer is implicit, and is not required unless there is ambiguity inside of your function; it adds noise to your code.

This:

constexpr primitive operator-() const noexcept { return -this->get(); }

Can be replaced with:

constexpr primitive operator-() const noexcept { return -get(); }

You can apply this to operator~() as well as operator!().


Replace long std::enable_if_t<> conditions with type traits.

In order to promote from a smaller type to a larger type, you currently enable certain conversion operators based on on your type T. These quickly become hard to maintain and are error-prone: you might forget to add a type, you might forget to update something, etc.

In order to solve this problem, you can take a type traits approach. It will require some boiler-plate, but not much more than what you've already got with those long enable-if conditions.

Traits based approach:

template<class T> struct type_alias { using type = T; };

template<class T> struct promote_arithmetic;

template<class T> using promote_arithmetic_t = typename promote_arithmetic<T>::type;

template<> struct promote_arithmetic<signed char> : type_alias<long> {};
template<> struct promote_arithmetic<short> : type_alias<long> {};
template<> struct promote_arithmetic<int> : type_alias<long> {};
template<> struct promote_arithmetic<long> : type_alias<long> {};

template<> struct promote_arithmetic<unsigned char> : type_alias<unsigned long> {};
template<> struct promote_arithmetic<unsigned short> : type_alias<unsigned long> {};
template<> struct promote_arithmetic<unsigned int> : type_alias<unsigned long> {};
template<> struct promote_arithmetic<unsigned long> : type_alias<unsigned long> {};

Now, back in primitive<T>, this code:

template <typename U = T, typename = std::enable_if_t< 
    std::is_same<U, signed char>::value
    || std::is_same<U, short>::value 
    || std::is_same<U, int>::value
>>
constexpr operator primitive<long>() const noexcept { return m_value; }

template <typename U = T, typename = std::enable_if_t< 
    std::is_same<U, unsigned char>::value 
    || std::is_same<U, unsigned short>::value
    || std::is_same<U, unsigned int>::value
>>
constexpr operator primitive<unsigned long>() const noexcept { return m_value; }

Becomes a one-liner:

constexpr operator primitive<promote_arithmetic_t<T>>() const noexcept { return m_value; }
//                           ^^^^^^^^^^^^^^^^^^^^^^^ using the new traits

Advantages of this approach:

  • Whenever you instantiate primitive<T>, you have the correct conversion operator (as defined by the traits).
  • Clarity. No more reading through multiple conditions that change for every conversion operator.
  • Maintenance, you now have one single function to debug and maintain. Additionally, you can easily add other conversions later without having to modify primitive<T> (as you pointed out yourself).
  • Code size, you no longer have multiple template parameters (or templates at all, in the case of the function).

You can similarly apply this to all your other applicable conversion operators. It will greatly reduce the amount of code inside your class.


\$\endgroup\$
5
  • \$\begingroup\$ Type traits it is! Can you further explain why I might want primitive<primitive<T>> and what the impact to the implementation would be? \$\endgroup\$ Sep 26, 2016 at 21:29
  • \$\begingroup\$ Started moving toward the type traits solution when I realized a signed char needs convert to multiple types. I will need multiple type trait classes for each type. \$\endgroup\$ Sep 26, 2016 at 21:58
  • \$\begingroup\$ @TravisParks I've removed that section since it was purely for ideas. \$\endgroup\$ Sep 26, 2016 at 23:45
  • \$\begingroup\$ I ended up using TFrom and TTo rather than just T. The specializations then inherit from type_alias<TTo>. \$\endgroup\$ Sep 27, 2016 at 0:35
  • 1
    \$\begingroup\$ I actually switched from a implicit conversion operator to an implicit constructor. I was running into issues where primitive<int> x(primitive<short>()) was not compiling. This gives much better error messages on MSVC and GCC, along the lines of "Could not convert primitive<double, void> to primitive<int, void>". \$\endgroup\$ Sep 27, 2016 at 12:32

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.