如何在不与标准库运算符冲突的情况下为一组相关的 类 模板重载运算符?
How do I template overload an operator for a group of related classes without conflicting with standard library operators?
这似乎是一个相当直接的想法:我有一组 classes,我应该能够为它们编写一个运算符,比方说减法,使用基本上完全相同的代码。
尝试以 "obvious" 方式执行此操作时,即:
template <typename T>
T operator-(T a, const T& b) {
return a -= b;
}
然后,在某些编译器上,这似乎与迭代器的减法运算符冲突(特别是它在 gcc 3.4.6 和 Apple LLVM 上中断,而在 gcc 版本 4 或更新版本上似乎工作正常),我得到了以下错误:
error: use of overloaded operator '-' is ambiguous (with operand types 'std::__1::__wrap_iter<const double *>' and 'const_iterator'
(aka '__wrap_iter<const_pointer>'))
我还考虑过仅对基 class 进行模板重载,组中的所有 classes 都将派生自该基,但是由于第一个参数是按值传递的,所以我认为特定于 subclass 的信息会在复制过程中丢失。
我是不是遗漏了什么明显的东西?有没有办法让所有这些编译器都满意?
编辑:理想情况下,该解决方案应该适用于 C++03。
将此模板和您的自定义数据结构放在同一个命名空间中(如果它们在根命名空间中 - 只需移动它们)。
由于 argument-dependent lookup,编译器将在您的结构的命名空间中查找此运算符,并且找到它没有问题和歧义。
在 C++11 中,std::enable_if
and std::is_base_of
可能对您有所帮助。假设你有一个基数 class Base
:
template <typename T, typename = std::enable_if_t<std::is_base_of<Base, T>::value>>
T operator-(T a, const T& b) {
return a -= b;
}
此处您的运算符只会被考虑用于派生自 Base
的类型 T
。
编辑:
cppreference.com 的链接页面显示了可能的实现,因此您也可以在 C++03 中使用它们,只需实现您自己的 enable_if
和 is_base_of
.
使用基础 class、adl koenig 运算符和 sfinae。
namespace ops{
struct subtract_support{
template<class T,
std::enable_if_t<std::is_base_of<subtract_support, T>{}, int> =0
>
friend T operator-( T lhs, T const& rhs ){
lhs-=rhs;
return lhs;
}
};
}
现在继承自 ops::subtract_support
使 -
适合您的类型。 (注意两行正文:确保 lhs 移出 -
,与您在 OP 中的版本不同)。
仅名称space 限制会导致您的 -
被发现任何模板生成的类型,其中一个 tge 参数来自您的名称space,以及其他意外情况:adl 操作模板成员来自特定名称的类型space.1
此技巧可让您在声明时将每种类型标记为使用此技术。
这种技术对二进制布局的影响几乎为零。但是,如果您需要一些晦涩的东西,例如前缀的布局兼容性,则可能需要特征 class。
现在,这是一个 C++11 解决方案。显然,OP 需要 C++03。好吧,最好的方法是像 C++11 一样实现它,这样当您升级编译器时就可以删除代码。
enable_if
可以很容易地用 C++03 编写。 is_base_of
需要多几行:
namespace notstd{
namespace details{
template<class T, class U>
struct base_test{
typedef char no; // sizeof(1)
struct yes { no unused[2]; }; // sizeof(2) or greater
static yes test(T*); // overload if arg is convertible-to-T*
static no test(...); // only picked if first overload fails
// pass a `U*` to `test`. If the result is `yes`, T
// is a base of U. Note that inaccessible bases might fail here(?)
// but we are notstd, good enough.
enum {value= (
sizeof(yes)==sizeof(test((U*)0))
)};
};
}
template<class Base, class T>
struct is_base_of{
enum{value=details::base_test<Base,T>::value};
};
template<bool b, class T=void>
struct enable_if {};
template<class T>
struct enable_if<true, T> {
typedef T type;
};
}
我们还需要调整模板 ADL 运算符中的 SFINAE 以符合 C++03:
namespace ops{
struct subtract_support{
template<class T>
friend
typename notstd::enable_if<notstd::is_base_of<subtract_support, T>::value, T>::type
operator-( T lhs, T const& rhs ){
lhs-=rhs;
return lhs;
}
};
}
1 例如,如果 Foo
是名称中的类型 space 具有贪婪的 -
模板运算符,则 decltype(v0-v1)
其中 v0
和 v1
是 vector<Foo>
将是 vector<Foo>
。这是一个误报(它不会编译)。但是 vec3<Foo>
(向量 space of 3 Foo)和它自己的 -
会导致同样的歧义。
遍历 您可以使用带有一个附加特征的简单 sfinea 来完成。 c++03 兼容代码:
namespace ops{
struct subtract_support{
template <class T>
struct subtract_support_trait {
typedef T type;
};
template<class T>
friend typename T::template subtract_support_trait<T>::type operator-( T lhs, T const& rhs ){
lhs-=rhs;
return lhs;
}
};
}
这似乎是一个相当直接的想法:我有一组 classes,我应该能够为它们编写一个运算符,比方说减法,使用基本上完全相同的代码。
尝试以 "obvious" 方式执行此操作时,即:
template <typename T>
T operator-(T a, const T& b) {
return a -= b;
}
然后,在某些编译器上,这似乎与迭代器的减法运算符冲突(特别是它在 gcc 3.4.6 和 Apple LLVM 上中断,而在 gcc 版本 4 或更新版本上似乎工作正常),我得到了以下错误:
error: use of overloaded operator '-' is ambiguous (with operand types 'std::__1::__wrap_iter<const double *>' and 'const_iterator'
(aka '__wrap_iter<const_pointer>'))
我还考虑过仅对基 class 进行模板重载,组中的所有 classes 都将派生自该基,但是由于第一个参数是按值传递的,所以我认为特定于 subclass 的信息会在复制过程中丢失。
我是不是遗漏了什么明显的东西?有没有办法让所有这些编译器都满意?
编辑:理想情况下,该解决方案应该适用于 C++03。
将此模板和您的自定义数据结构放在同一个命名空间中(如果它们在根命名空间中 - 只需移动它们)。
由于 argument-dependent lookup,编译器将在您的结构的命名空间中查找此运算符,并且找到它没有问题和歧义。
在 C++11 中,std::enable_if
and std::is_base_of
可能对您有所帮助。假设你有一个基数 class Base
:
template <typename T, typename = std::enable_if_t<std::is_base_of<Base, T>::value>>
T operator-(T a, const T& b) {
return a -= b;
}
此处您的运算符只会被考虑用于派生自 Base
的类型 T
。
编辑:
cppreference.com 的链接页面显示了可能的实现,因此您也可以在 C++03 中使用它们,只需实现您自己的 enable_if
和 is_base_of
.
使用基础 class、adl koenig 运算符和 sfinae。
namespace ops{
struct subtract_support{
template<class T,
std::enable_if_t<std::is_base_of<subtract_support, T>{}, int> =0
>
friend T operator-( T lhs, T const& rhs ){
lhs-=rhs;
return lhs;
}
};
}
现在继承自 ops::subtract_support
使 -
适合您的类型。 (注意两行正文:确保 lhs 移出 -
,与您在 OP 中的版本不同)。
仅名称space 限制会导致您的 -
被发现任何模板生成的类型,其中一个 tge 参数来自您的名称space,以及其他意外情况:adl 操作模板成员来自特定名称的类型space.1
此技巧可让您在声明时将每种类型标记为使用此技术。
这种技术对二进制布局的影响几乎为零。但是,如果您需要一些晦涩的东西,例如前缀的布局兼容性,则可能需要特征 class。
现在,这是一个 C++11 解决方案。显然,OP 需要 C++03。好吧,最好的方法是像 C++11 一样实现它,这样当您升级编译器时就可以删除代码。
enable_if
可以很容易地用 C++03 编写。 is_base_of
需要多几行:
namespace notstd{
namespace details{
template<class T, class U>
struct base_test{
typedef char no; // sizeof(1)
struct yes { no unused[2]; }; // sizeof(2) or greater
static yes test(T*); // overload if arg is convertible-to-T*
static no test(...); // only picked if first overload fails
// pass a `U*` to `test`. If the result is `yes`, T
// is a base of U. Note that inaccessible bases might fail here(?)
// but we are notstd, good enough.
enum {value= (
sizeof(yes)==sizeof(test((U*)0))
)};
};
}
template<class Base, class T>
struct is_base_of{
enum{value=details::base_test<Base,T>::value};
};
template<bool b, class T=void>
struct enable_if {};
template<class T>
struct enable_if<true, T> {
typedef T type;
};
}
我们还需要调整模板 ADL 运算符中的 SFINAE 以符合 C++03:
namespace ops{
struct subtract_support{
template<class T>
friend
typename notstd::enable_if<notstd::is_base_of<subtract_support, T>::value, T>::type
operator-( T lhs, T const& rhs ){
lhs-=rhs;
return lhs;
}
};
}
1 例如,如果 Foo
是名称中的类型 space 具有贪婪的 -
模板运算符,则 decltype(v0-v1)
其中 v0
和 v1
是 vector<Foo>
将是 vector<Foo>
。这是一个误报(它不会编译)。但是 vec3<Foo>
(向量 space of 3 Foo)和它自己的 -
会导致同样的歧义。
遍历
namespace ops{
struct subtract_support{
template <class T>
struct subtract_support_trait {
typedef T type;
};
template<class T>
friend typename T::template subtract_support_trait<T>::type operator-( T lhs, T const& rhs ){
lhs-=rhs;
return lhs;
}
};
}