总的来说,我正在实现一个类来模块化无符号数(无符号类型的包装器)。我希望我的类(class)满足以下条件:
- 执行模数可以执行的所有操作。
- 带下划线的value_type必须是无符号类型,但模板参数可以转换为无符号类型(例如,如果模板参数类型为int,则带下划线的value_type将为无符号类型)。
- 我想要一个可以执行额外操作(例如除法)的派生类。
- 它必须承认操作与可转换为 value_type 的类型兼容(例如,
Test<N> + unsigned
、Test<N> + UnsignedWrapper
、...)。 - 必须允许隐式转换为带下划线的 value_type。
- 必须允许从
Test<N>
进行显式转换至Test<K>
(对于 TestChild 也是如此),是 N 和 K 个不同的数字。
我将我的问题简化为这段代码:
测试.hpp:
#include <type_traits>
#include <iostream>
// As you can see, C++17 is needed
template <auto UInt>
class Test{
public:
//I need the type to be unsigned
typedef std::make_unsigned_t<decltype(UInt)> value_type;
static constexpr value_type N = static_cast<value_type>(UInt);
Test (const value_type& k = 0) : n(k%N){
// Just to show that this constructor is always called
std::cout << "Test user-defined constructor " << k << std::endl;
}
Test& operator+= (const Test& k){
n = (n+k.n)%N;
return *this;
}
template<typename U>
Test& operator+= (const U& other){
n = (n+static_cast<value_type>(other))%N;
return *this;
}
template<auto UInt2>
explicit operator Test<UInt2>() const{
return Test<UInt2>(n);
}
operator value_type() const{
// Just to show that this is called only once
std::cout << "Casting to value_type" << std::endl;
return n;
}
protected:
value_type n;
};
template<auto UInt>
class TestChild : public Test<UInt>{
public:
typedef typename Test<UInt>::value_type value_type;
static constexpr value_type N = Test<UInt>::N;
TestChild (const value_type& k = 0) : Test<UInt>(k){}
template<auto UInt2>
explicit operator TestChild<UInt2>() const{
return TestChild<UInt2>(this->n);
}
};
// I prefer to define binary operators outside the class and leave the logic inside the class
template<auto UInt>
const Test<UInt> operator+ (const Test<UInt>& lhs, const Test<UInt>& rhs){
return Test<UInt>(lhs) += rhs;
}
template<auto UInt>
const TestChild<UInt> operator+ (const TestChild<UInt>& lhs, const TestChild<UInt>& rhs){
return TestChild<UInt>(lhs) += rhs;
}
template<auto UInt, typename U>
const Test<UInt> operator+ (const Test<UInt>& lhs, const U& rhs){
return Test<UInt>(lhs) += static_cast<typename Test<UInt>::value_type>(rhs);
}
template<auto UInt, typename U>
const Test<UInt> operator+ (const U& lhs, const Test<UInt>& rhs){
return Test<UInt>(rhs) += static_cast<typename Test<UInt>::value_type>(lhs);
}
/****************************************************************************/
int main(){
// It doesn't matter how I initialize the varible,
// always calls the user-defined constructor
TestChild<89209> x(347), y(100), z(1000);
TestChild<89133> t = static_cast<decltype(t)>(x);
Test<10000> u = static_cast<decltype(u)>(y), v(z);
Test<19847> w(u);
TestChild<1297> r(u); //Here it seems that it casts u to its member value_type
u = u + v;
//u = u + w; //The compiler complains about ambiguity (don't know how to fix it without casting w)
//x = y + z; //No idea what's happening here
}
如果我取消对最后两个总和的注释,在使用 g++ -std=c++17 -O2 -Wall -Wextra -pedantic test.cpp -o test
编译时会出现此错误,在 Ubuntu 16.04 LTS 中使用 GCC 7.2.0:
test.cpp: In function ‘int main()’:
test.cpp:92:10: error: ambiguous overload for ‘operator+’ (operand types are ‘Test<10000>’ and ‘Test<19847>’)
u = u + w; //The compiler complains about ambiguity (don't know how to fix it without casting w)
~~^~~
test.cpp:92:10: note: candidate: operator+(Test<10000>::value_type {aka unsigned int}, Test<19847>::value_type {aka unsigned int}) <built-in>
test.cpp:69:18: note: candidate: const Test<UInt> operator+(const Test<UInt>&, const U&) [with auto UInt = 10000; U = Test<19847>]
const Test<UInt> operator+ (const Test<UInt>& lhs, const U& rhs){
^~~~~~~~
test.cpp:74:18: note: candidate: const Test<UInt> operator+(const U&, const Test<UInt>&) [with auto UInt = 19847; U = Test<10000>]
const Test<UInt> operator+ (const U& lhs, const Test<UInt>& rhs){
^~~~~~~~
test.cpp: In instantiation of ‘const TestChild<UInt> operator+(const TestChild<UInt>&, const TestChild<UInt>&) [with auto UInt = 89209]’:
test.cpp:93:12: required from here
test.cpp:65:35: error: could not convert ‘(* & lhs).TestChild<89209>::<anonymous>.Test<89209>::operator+=<TestChild<89209> >((* & rhs))’ from ‘Test<89209>’ to ‘const TestChild<89209>’
return TestChild<UInt>(lhs) += rhs;
^~~
如您所见,我如何初始化变量并不重要;始终调用用户定义的构造函数。我最初认为调用了对带下划线的 value_type 的强制转换,但后来我意识到情况并非如此,因为我将强制转换显式转换为 value_type 但它仍然不执行任何强制转换。我认为编译器一定做了一些奇怪的事情来避免复制构造函数或复制赋值,但我真的不知道。
我理解 u = u + w;
中的歧义,可以通过类型转换 w 来固定;但我想找到一种无需强制转换的方法(我的意思是,也许可以推断 u 属于一种类型,因此总和必须返回该类型)。
第二个总和是我不太明白编译器错误是什么意思的那个。我一直在寻找一个星期左右的解决方案,但我不知道该怎么做。它似乎获得了正确的函数签名(调用了正确的 operator+ 重载),但随后提示一些奇怪的类型转换。
也许我在类设计上有所欠缺。如果是这样,请告诉我。
编辑:请注意 u = w + u
也应该有效,但它会导致另一个歧义,所以我决定强制转换以进行操作。
最佳答案
这个:
u = u + w;
不明确,因为你有两个 operator+()
匹配良好的重载:
template<auto UInt, typename U>
const Test<UInt> operator+(const Test<UInt>& lhs, const U& rhs);
template<auto UInt, typename U>
const Test<UInt> operator+(const U& lhs, const Test<UInt>& rhs);
它们都不比另一个更专业,因此编译器无法知道调用哪个(旁注,不要返回 const
纯右值)。这里最简单的解决方案是只添加第三个重载,它比两者都更专业:
template<auto UInt, auto UInt2>
Test<UInt> operator+(const Test<UInt>& lhs, const Test<UInt2>& rhs);
一个不太简单的解决方案是限制一个或另一个 - 不是是 Test
:
template <typename T> struct is_test : std::false_type { };
template <auto V> struct is_test<Test<V>> : std::true_type { };
template<auto UInt, typename U,
std::enable_if_t<!is_test<U>{}, int> = 0>
Test<UInt> operator+(const U& lhs, const Test<UInt>& rhs);
但这似乎过于复杂,可能没有必要。
这个:
x = y + z;
会调用:
template<auto UInt>
const TestChild<UInt> operator+ (const TestChild<UInt>& lhs, const TestChild<UInt>& rhs);
依次调用 operator+=
在左侧的拷贝上 - 实际上是 Test<U>::operator+=
,它返回一个 Test<U>
.
然而,operator+
你正在调用返回 TestChild<U>
,这是一个派生类 - 和 Test<U>
不能隐式转换为 TestChild<U>
.这里的简单解决方法是只执行加法,但将返回分开:
template<auto UInt>
TestChild<UInt> operator+ (TestChild<UInt> lhs, const TestChild<UInt>& rhs) {
lhs += rhs;
return lhs;
}
现在一切都可以编译了。
关于C++17:始终调用用户定义的构造函数并且 operator+ 重载不适用于子类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47413886/