The following piece of code can be compiled using MSVC(v19.38), but failed with GCC(13.2) and Clang(17.0.1). But older version of GCC, e.g. 11.2, works fine.
#include <type_traits>
#ifdef MY_INT
template<typename T, T V> struct my_int
{
static constexpr T value = V;
using value_type = T;
constexpr operator value_type() const noexcept { return value; }
constexpr value_type operator()() const noexcept { return value; }
};
template<auto V> using constant_int_t = my_int<decltype(V), V>;
#else
template<auto V> using constant_int_t = std::integral_constant<decltype(V), V>;
#endif
template<typename T1, typename T2>
using add_t = decltype(T1{} + T2{});
template<typename T1, typename T2>
constexpr auto operator+(T1, T2)
{
return constant_int_t<T1::value + T2::value>{};
}
using v1 = constant_int_t<1>;
using v2 = constant_int_t<2>;
using v3 = add_t<v1, v2>;
static_assert(std::is_same_v<v3, constant_int_t<3>>);
Clang complains about v3
is of type int, which might be ascribed to it fails to spot the operator+
defined after termplate alias add_t
and performed an implicit type conversion when dealing with the adding.
If I use my own type of constant int (my_int<T,V>
, which is almost the same as std::integral_constant<T,V>
), clang will compile it. If I swap the order of definition of add_t
and operator+
, it also works fine.
So, is this a bug of Clang and GCC? Or an unspecified behaviour?
When you use my_int
, the ::
(global) namespace is an associated namespace (since my_int
is defined in the ::
namespace), so auto ::operator+(T1, T2);
can be found via ADL.
If you use std::integral_constant<int, x>
, the only associated namespace is std
. Therefore, operator+
is looked up in the std::
namespace for ADL, which doesn't find anything that applies (so the builtin int operator+(int, int)
is used after converting the arguments)
If you moved the operator+
definition before add_t
, it can be found via ordinary/non-ADL lookup.