c++c++17constexpr

Can I detect an integer type without listing all of them as template specializations?


I am playing around with implementing C++ std library traits. I could implement is_integral like this:

template <typename T>
is_integral
{
   static constexpr bool value = false;
}
template <>
is_integral<int>
{
   static constexpr bool value = true;
}
// do this for like 10 other types, at least

I was trying to detect integers with reasonable reliability using two things:

Other could be added. But I am having trouble templating this. For this to compile, I need to avoid errors when / or & are not defined for T.

I tried this:

#if __cplusplus > 199711L
namespace impl
{
  // type that is returned for X op Y when operator op is missing
  struct NoOperator {}; 
  // Out of line operator for any X & Y
  template<typename T, typename Arg> NoOperator operator& (const T&, const Arg&);

  template<typename T, typename Arg = T>
  struct BitAndExists
  {
    // check if the result of bit and is our NoOperator type
    enum { value = !is_same<decltype(declval<T>() & declval<Arg>()), NoOperator>::value };
  };  
}
#endif

namespace impl
{
    template <typename T>
    constexpr bool has_integer_bit_and()
    {
        if constexpr (impl::BitAndExists<T>::value && !is_floating_point<T>::value)
        {
            return (T(0) & T(1)) == T(0);
        }
        else
        {
            return false;
        }
    }
    template <typename T>
    struct integral_checks {
        static constexpr bool bit_and_check = has_integer_bit_and<T>();
        static constexpr bool round_check = (T(3)/T(2)) == T(1);

        static constexpr bool value = bit_and_check && round_check;
    };
    template <typename T>
    struct integral_checks<T*> {
        static constexpr bool bit_and_check = false;
        static constexpr bool round_check = false;
        static constexpr bool value = false;
    };
    template <typename T>
    struct integral_checks<T&> {
        static constexpr bool bit_and_check = false;
        static constexpr bool round_check = false;
        static constexpr bool value = false;
    };
}

template<typename T>
struct is_integral
{
    static constexpr bool value = impl::integral_checks<T>::value;
};

But I am getting multiple errors:

error: invalid operands of types 'float' and 'float' to binary 'operator&'
  101 |     enum { value = !is_same<decltype(declval<T>() & declval<Arg>()), NoOperator>::value };

So this means the operator trick didn't work as intended.

Constexpr variable 'bit_and_check' must be initialized by a constant expression

This is more confusing, I'd used assignment from constexpr () to constexpr variable without problems, even with complex types.

Here is full code sample: https://godbolt.org/z/Ys9bqKq4a

Note that I am doing this as an exercise. I understand that duck-typing an int like this could have some nasty unintended consequences. But I want to learn from the errors above.


Solution

  • The problem of your code is that your struct BitAndExist

    template<typename T, typename Arg = T>
    struct BitAndExists
    {
      // check if the result of bit and is our NoOperator type
      enum { value = !std::is_same<decltype(std::declval<T>() & std::declval<Arg>()), NoOperator>::value };
    };
    

    requires (decltype(std::declval<T>() & std::declval<Arg>()) that the operator & between a T value and an Arg value is defined. Otherwise you obtain an error.

    You have to place the same expression, or something similar, in a context where you can use SFINAE (Substitution Failure Is Not An Error). For example, in a template method, as follows

    template <typename T>
    struct is_integral
    {
      template <typename U>
      static constexpr decltype((U(3) / U(2)), (U(1) & U(0)), bool{}) check (int)
      { return ((U(3) / U(2)) == U(1)) && ((U(0) & U(1)) == U(0)); }
    
      template <typename>
      static constexpr bool check (long)
      { return false; }
    
      static constexpr bool value = check<T>(0);
    };
    
    int main ()
    {
      static_assert(!is_integral<float>::value, "is_integral detection error");
      static_assert(!is_integral<bool*>::value, "is_integral detection error");
      static_assert(!is_integral<bool&>::value, "is_integral detection error");
      static_assert(is_integral<int>::value, "is_integral detection error");
      static_assert(is_integral<char>::value, "is_integral detection error");
    }