关于模板别名和用户定义运算符的顺序的奇怪行为

qzh*_*ong 4 c++ name-lookup template-aliases clang++

以下代码可以使用 MSVC(v19.38) 编译,但使用 GCC(13.2) 和 Clang(17.0.1) 编译失败。但旧版本的 GCC,例如 11.2,可以正常工作。

#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>>);

Run Code Online (Sandbox Code Playgroud)

Clang 抱怨v3is 类型为 int,这可能是因为它无法识别operator+在 termplate 别名后定义的内容add_t,并在处理添加时执行了隐式类型转换。

如果我使用我自己的常量 int 类型(my_int<T,V>与 几乎相同std::integral_constant<T,V>),clang 将编译它。add_t如果我交换和的定义顺序operator+,它也可以正常工作。

那么,这是 Clang 和 GCC 的 bug 吗?还是一种未明确的行为?

Art*_*yer 5

当您使用 时my_int,::(全局)命名空间是关联的命名空间(因为my_int是在::命名空间中定义的),因此auto ::operator+(T1, T2);可以通过 ADL 找到。

如果使用std::integral_constant<int, x>,则唯一关联的命名空间是std. 因此,operator+在 ADL 的命名空间中查找std::,没有找到任何适用的内容(因此int operator+(int, int)在转换参数后使用内置函数)

如果您operator+之前移动了定义add_t,则可以通过普通/非 ADL 查找找到它。

  • @qzhong 答案是正确的。模板只能看到在其定义之前声明的函数,ADL 除外,它可以找到在任何地方声明的函数。来源:https://en.cppreference.com/w/cpp/language/unqualified_lookup#Template_definition *“因为模板在专业化之前不会变成真正的‘代码’。”*编译器通常会在以下情况下“半烘焙”模板:他们首先看到定义(尝试检查一些无需实例化即可检测到的错误,但检查的数量因编译器而异)。 (2认同)