简体   繁体   中英

C++17: user-defined constructor is always called and operator+ overloading not working for child class

Overall, I'm implementing a class to modularize an unsigned number (a wrapper for unsigned types). I want my class to meet this conditions:

  • Do all the operations a modular number can do.
  • The underlined value_type must be of unsigned type, but the template parameter can be convertible to an unsigned type (for example, if the template parameter type is int, then the underlined value_type will be unsigned).
  • I want a derived class that can perfoms extra operations (like dividing, for example).
  • It must admit the operations to be compatible with types convertible to value_type (for example, Test<N> + unsigned , Test<N> + UnsignedWrapper , ...).
  • Must allow implicit conversion to the underlined value_type.
  • Must allow explicit conversions from 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM