Search code examples
c++clang++name-lookuptemplate-aliases

Weird behaviour about the order of template alias and user defined operator


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?


Solution

  • 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.