C ++ 17:始终调用用户定义的构造函数,并且operator +重载不适用于子类

时间:2017-11-21 13:07:26

标签: c++ inheritance constructor c++17

总的来说,我正在实现一个类来模块化无符号数(无符号类型的包装器)。我希望我的班级能够满足这个条件:

  • 执行模块化编号可以执行的所有操作。
  • 带下划线的value_type必须是无符号类型,但模板参数可以转换为无符号类型(例如,如果模板参数类型为int,则带下划线的value_type将是无符号的。)
  • 我想要一个可以执行额外操作的派生类(比如分割)。
  • 必须承认操作与可转换为value_type的类型兼容(例如,Test<N> + unsignedTest<N> + UnsignedWrapper,...)。
  • 必须允许隐式转换为带下划线的value_type。
  • 必须允许从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也应该有效,但这会导致另一种歧义,因此我决定强行进行投射以进行操作。

1 个答案:

答案 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;
}

现在一切都在编译。