Reputation: 53
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
Reputation: 15918
I think this is a bug of Clang.
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.
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
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