Overall, I'm implementing a class to modularize an unsigned number (a wrapper for unsigned types). I want my class to meet this conditions:
Test<N> + unsigned
, Test<N> + UnsignedWrapper
, ...). Test<N>
to Test<K>
(same for TestChild), being N and K different numbers. I reduced my problem to this code:
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
}
If I uncomment the last two sums, I get this error when compiling with g++ -std=c++17 -O2 -Wall -Wextra -pedantic test.cpp -o test
, with GCC 7.2.0 in Ubuntu 16.04 LTS:
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;
^~~
As you can see, it doesn't matter how I initialize the variables; the user-define constructor is always called. I initially thought that a casting to the underlined value_type was called, but then I realized that's not the case, because I explicited the casting to value_type and it still doesn't do any casting. I think the compiler must be doing something weird to avoid the copy constuctor or copy assignment, but I don't really know.
I understand the ambiguity in u = u + w;
, which can be fixed by casting w; but I'd like to find a way to do it without casting (I mean, maybe deducing that u is of one type, so the sum must return that type).
The second sum is the one I don't really understand what the compiler error means. I've been looking for a solution for a week or so, and I can't figure out what to do. It seems that it gets the function signature right (the correct overload of operator+ is called), but then complains about some weird type conversion.
Maybe I'm laking in the design of the class. If that's the case, let me know.
EDIT: Note that u = w + u
should also work, but it leads to another ambiguity, so I'm deciding to force the casting in order to do the operation.
This:
u = u + w;
is ambiguous because you have two operator+()
overloads that are equall good matches:
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);
Neither of those is more specialized than the other, so there's no way for the compiler to know which one to call (side-note, don't return const
prvalues). The simplest solution here is to just add a 3rd overload which is more specialized than both:
template<auto UInt, auto UInt2>
Test<UInt> operator+(const Test<UInt>& lhs, const Test<UInt2>& rhs);
A less simple solution would be to constrain one or the other - on not being a 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);
But that seems overly complicated, probably unnecessary.
This:
x = y + z;
would invoke:
template<auto UInt>
const TestChild<UInt> operator+ (const TestChild<UInt>& lhs, const TestChild<UInt>& rhs);
which in turn calls operator+=
on a copy of the left-hand side - which is actually Test<U>::operator+=
, which returns a Test<U>
.
However, the operator+
you're calling returns a TestChild<U>
, which is a derived class - and Test<U>
is not implicitly convertible to TestChild<U>
. The easy fix here is to just perform the addition, but separate the return:
template<auto UInt>
TestChild<UInt> operator+ (TestChild<UInt> lhs, const TestChild<UInt>& rhs) {
lhs += rhs;
return lhs;
}
And now everything compiles.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.