C++ 运算符重载和关联的命名空间
C++ operator overloading and associated namespace
以下简化示例在 gcc
和 Visual Studio
中编译,但在 clang
中失败!?
namespace N
{
struct A {};
template <typename T>
double operator+ (T a, double d) {return d;}
template <typename T>
double operator+ (double d, T a) {return d;}
}
void test()
{
N::A a;
double x;
double y = a + x;
double z = x + a;
}
据我所知,命名空间 N
中的模板化 operator+
应该可以被 ADL 找到。
为什么 clang
不同意?它是 clang
或其他编译器中的错误吗?
这里是clang 3.5.1的编译错误(在coliru上测试),我不明白这里有什么问题...
10 : error: overloaded 'operator+' must have at least one parameter of class or enumeration type
double operator+ (double d, T a) {return d;}
^
18 : note: in instantiation of function template specialization 'N::operator+' requested here
double y = a + x;
^
7 : error: overloaded 'operator+' must have at least one parameter of class or enumeration type
double operator+ (T a, double d) {return d;}
^
19 : note: in instantiation of function template specialization 'N::operator+' requested here
double z = x + a;
^
2 errors generated.
Compilation failed
当然,这个例子是从现实生活中的代码中简化而来的。目的是在命名空间 N 内定义的任何 class 都有一个重载的 operator+ 和 double.
它可能会抱怨,因为 T
可能 不是该定义中的 class。并且不允许为算术类型 IIRC 重新定义标准 operator+
。例如,在您的示例中,没有任何限制 T
为 N::A
。
添加 typename = std::enable_if_t<std::is_class<T>{} || std::is_enum<T>{}>
似乎可以解决问题。 Visual Studio 和 GCC 可能 lax/lazy 关于此限制。
namespace N
{
struct A {};
template <typename T, typename = std::enable_if_t<std::is_class<T>{} || std::is_enum<T>{}>>
double operator+ (T a, double d) {return d;}
template <typename T, typename = std::enable_if_t<std::is_class<T>{} || std::is_enum<T>{}>>
double operator+ (double d, T a) {return d;}
}
void test()
{
N::A a;
double x;
double y = a + x;
double z = x + a;
}
这是由两个不同的 CWG 问题引起的:CWG issue 2052 and CWG issue 1391。
首先,CWG 1391。在遇到 x + a
时,通常的名称查找会发现
template <typename T> double operator+ (T, double);
模板参数推导是通过匹配 T
到 +
的 lhs 类型 double
,所以这推导 T
为 double
。第二个参数的类型不包含模板参数,因此在当前规则下不被考虑。可以肯定的是,N::A
不能转换为 double
,因此生成的特化是不可行的,但是当前的规则说模板参数推导不关心这个;将在重载决议中处理。
CWG 1391 的拟议决议除其他事项外,还在标准中添加了一个新段落:
If deduction succeeds for all parameters that contain
template-parameters that participate in template argument deduction,
and all template arguments are explicitly specified, deduced, or
obtained from default template arguments, remaining parameters are
then compared with the corresponding arguments. For each remaining
parameter P
with a type that was non-dependent before substitution of
any explicitly-specified template arguments, if the corresponding
argument A cannot be implicitly converted to P
, deduction fails.
[Note: Parameters with dependent types in which no template-parameters
participate in template argument deduction, and parameters that became
non-dependent due to substitution of explicitly-specified template
arguments, will be checked during overload resolution. —end note]
换句话说,如果一个参数(a
在我们的例子中)对应于一个非依赖参数(double
)不能转换为参数的类型,推导就会失败。所以在我们的例子中,post-CWG1391 模板参数推导将因这个重载而失败,一切都会好起来的。
但是,Clang 实现了当前规则,因此推导成功 T = double
,替换发生,我们遇到了 CWG 2052。引用 Richard Smith(Clang 开发者)的文章:
In an example like
struct A { operator int(); };
template<typename T> T operator<<(T, int);
void f(A a) { 1 << a; }
Template argument deduction succeeds for the operator template,
producing the signature operator<<(int,int)
. The resulting
declaration is synthesized and added to the overload set, per 14.8.3
[temp.over] paragraph 1. However, this violates the requirement of
13.5 [over.oper] paragraph 6,
An operator function shall either be a non-static member function or
be a non-member function that has at least one parameter whose type is
a class, a reference to a class, an enumeration, or a reference to an
enumeration.
这不是 SFINAE 上下文,因此程序格式错误,而是
而不是选择内置运算符。
在这种情况下,没有转换,所以推导的operator+(double, double)
实际上是不可行的,但是直到你建立了候选集,不可行的候选人才被淘汰,而这里建立候选集是造成一个硬错误。
向 CWG 2052 提出的决议将使这种情况改为 SFINAE - 也使原始代码有效。问题是 - Clang 也在此处实施标准的当前版本。
以下简化示例在 gcc
和 Visual Studio
中编译,但在 clang
中失败!?
namespace N
{
struct A {};
template <typename T>
double operator+ (T a, double d) {return d;}
template <typename T>
double operator+ (double d, T a) {return d;}
}
void test()
{
N::A a;
double x;
double y = a + x;
double z = x + a;
}
据我所知,命名空间 N
中的模板化 operator+
应该可以被 ADL 找到。
为什么 clang
不同意?它是 clang
或其他编译器中的错误吗?
这里是clang 3.5.1的编译错误(在coliru上测试),我不明白这里有什么问题...
10 : error: overloaded 'operator+' must have at least one parameter of class or enumeration type
double operator+ (double d, T a) {return d;}
^
18 : note: in instantiation of function template specialization 'N::operator+' requested here
double y = a + x;
^
7 : error: overloaded 'operator+' must have at least one parameter of class or enumeration type
double operator+ (T a, double d) {return d;}
^
19 : note: in instantiation of function template specialization 'N::operator+' requested here
double z = x + a;
^
2 errors generated.
Compilation failed
当然,这个例子是从现实生活中的代码中简化而来的。目的是在命名空间 N 内定义的任何 class 都有一个重载的 operator+ 和 double.
它可能会抱怨,因为 T
可能 不是该定义中的 class。并且不允许为算术类型 IIRC 重新定义标准 operator+
。例如,在您的示例中,没有任何限制 T
为 N::A
。
添加 typename = std::enable_if_t<std::is_class<T>{} || std::is_enum<T>{}>
似乎可以解决问题。 Visual Studio 和 GCC 可能 lax/lazy 关于此限制。
namespace N
{
struct A {};
template <typename T, typename = std::enable_if_t<std::is_class<T>{} || std::is_enum<T>{}>>
double operator+ (T a, double d) {return d;}
template <typename T, typename = std::enable_if_t<std::is_class<T>{} || std::is_enum<T>{}>>
double operator+ (double d, T a) {return d;}
}
void test()
{
N::A a;
double x;
double y = a + x;
double z = x + a;
}
这是由两个不同的 CWG 问题引起的:CWG issue 2052 and CWG issue 1391。
首先,CWG 1391。在遇到 x + a
时,通常的名称查找会发现
template <typename T> double operator+ (T, double);
模板参数推导是通过匹配 T
到 +
的 lhs 类型 double
,所以这推导 T
为 double
。第二个参数的类型不包含模板参数,因此在当前规则下不被考虑。可以肯定的是,N::A
不能转换为 double
,因此生成的特化是不可行的,但是当前的规则说模板参数推导不关心这个;将在重载决议中处理。
CWG 1391 的拟议决议除其他事项外,还在标准中添加了一个新段落:
If deduction succeeds for all parameters that contain template-parameters that participate in template argument deduction, and all template arguments are explicitly specified, deduced, or obtained from default template arguments, remaining parameters are then compared with the corresponding arguments. For each remaining parameter
P
with a type that was non-dependent before substitution of any explicitly-specified template arguments, if the corresponding argument A cannot be implicitly converted toP
, deduction fails. [Note: Parameters with dependent types in which no template-parameters participate in template argument deduction, and parameters that became non-dependent due to substitution of explicitly-specified template arguments, will be checked during overload resolution. —end note]
换句话说,如果一个参数(a
在我们的例子中)对应于一个非依赖参数(double
)不能转换为参数的类型,推导就会失败。所以在我们的例子中,post-CWG1391 模板参数推导将因这个重载而失败,一切都会好起来的。
但是,Clang 实现了当前规则,因此推导成功 T = double
,替换发生,我们遇到了 CWG 2052。引用 Richard Smith(Clang 开发者)的文章:
In an example like
struct A { operator int(); }; template<typename T> T operator<<(T, int); void f(A a) { 1 << a; }
Template argument deduction succeeds for the operator template, producing the signature
operator<<(int,int)
. The resulting declaration is synthesized and added to the overload set, per 14.8.3 [temp.over] paragraph 1. However, this violates the requirement of 13.5 [over.oper] paragraph 6,An operator function shall either be a non-static member function or be a non-member function that has at least one parameter whose type is a class, a reference to a class, an enumeration, or a reference to an enumeration.
这不是 SFINAE 上下文,因此程序格式错误,而是 而不是选择内置运算符。
在这种情况下,没有转换,所以推导的operator+(double, double)
实际上是不可行的,但是直到你建立了候选集,不可行的候选人才被淘汰,而这里建立候选集是造成一个硬错误。
向 CWG 2052 提出的决议将使这种情况改为 SFINAE - 也使原始代码有效。问题是 - Clang 也在此处实施标准的当前版本。