总的来说,我正在实现一个类来模块化无符号数(无符号类型的包装器)。我希望我的班级能够满足这个条件:
Test<N> + unsigned
,Test<N> + UnsignedWrapper
,...)。Test<N>
到Test<K>
的显式转换(与TestChild相同),即N和K不同的数字。我将问题减少到了这段代码:
test.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;
^~~
正如您所看到的,我如何初始化变量并不重要;始终调用user-define构造函数。我最初认为调用了带下划线的value_type的转换,但后来我意识到情况并非如此,因为我将转换表示为value_type并且它仍然没有进行任何转换。我认为编译器必须做一些奇怪的事情来避免复制构造或复制赋值,但我真的不知道。
我理解u = u + w;
中的歧义,可以通过施放w来修复;但是我想找到一种方法来做到这一点而不进行投射(我的意思是,可能推断出你是一种类型,所以总和必须返回那种类型)。
第二个总和是我真的不明白编译器错误意味着什么。我一直在寻找一个星期左右的解决方案,我无法弄清楚该怎么做。似乎它正确地获取了函数签名(调用了operator +的正确重载),但是却抱怨了一些奇怪的类型转换。
也许我在课堂上设计。如果是这种情况,请告诉我。
编辑:请注意,u = w + u
也应该有效,但这会导致另一种歧义,因此我决定强行进行投射以进行操作。
答案 0 :(得分:3)
此:
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
prvalues)。这里最简单的解决方案是添加第三个重载,它比两者都更专业:
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;
}
现在一切都在编译。