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:
(T(3)/T(2)) == T(1)
(T(0) & T(1)) == T(0)
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.
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");
}