qzhong
qzhong

Reputation: 53

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?

Upvotes: 4

Views: 150

Answers (2)

cpplearner
cpplearner

Reputation: 15918

I think this is a bug of Clang.

[temp.alias]/2:

When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the defining-type-id of the alias template.

So

using v3 = add_t<v1, v2>;

should be equivalent to

using v3 = decltype(v1{} + v2{});

But Clang treats them as different types.


Note that alias templates, unlike all other kinds of templates, are formally not subject to template instantiation. Therefore, in my opinion, the fact that only ADL (argument-dependent lookup) is performed at the point of instantiation is irrelevant here.

[basic.lookup.argdep]/4:

If the lookup is for a dependent name, [argument-dependent] lookup is also performed from each point in the instantiation context of the lookup [...]

[temp.spec.general]/1 (note the omission of "alias template"):

The act of instantiating a function, a variable, a class, a member of a class template, or a member template is referred to as template instantiation.

[temp.dep.candidate]/1 (which IMO is not applicable to alias templates because they are not instantiated):

If a dependent call would be ill-formed or would find a better match had the lookup for its dependent name considered all the function declarations with external linkage introduced in the associated namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program is ill-formed, no diagnostic required.

Upvotes: 3

Artyer
Artyer

Reputation: 40891

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.

Upvotes: 5

Related Questions