3

I am reviewing operator overloading in C++. Just for fun I am implementing a BigInt class.

The first operator I want to overload for it is the addition operator. I have decided to overload this operator as a friend non-member function. Here's a MWE of this code:

#include <cassert>
#include <iostream>
#include <string>

class BigInt{
 public:
  friend BigInt operator+(const BigInt &bi1, const BigInt &bi2);

  BigInt() {}
  explicit BigInt(const std::string &in) {
    if (in.size() != 0) {
      for (auto cc = in.rbegin(); cc != in.rend(); ++cc) {
        value_.push_back(*cc);
      }
    }
  }
  std::string value() {
    std::string actual_value{};  // Reversed string.
    for (auto cc = value_.rbegin(); cc != value_.rend(); ++cc) {
      actual_value.push_back(*cc);
    }
    return actual_value;
  }

 private:
  std::string value_;  // String of digits as characters.
};

BigInt operator+(const BigInt &bi1, const BigInt &bi2) {
  BigInt result{};

  result.value_ = "4421";
  return result;
}

int main() {
  std::cout << "Test addition operator... ";
  std::string number{"1234"};  // Number 1,234.
  BigInt mm(number);
  std::string number_ten{"10"};  // Number 10.
  BigInt nn(number_ten);

  BigInt mm_nn = mm + nn;

  std::string expected_result{"1244"};  // 1,234 + 10 = 1,244.
  assert(mm_nn.value() == expected_result);
  std::cout << "ok." << std::endl;
}

This code mocks the behavior of the addition. It compiles and runs. Yet when I add a copy constructor for the BigInt class, this codes stops working. I.e. if I add this to the class declaration:

explicit BigInt(const BigInt &in): value_(in.value_) {}

The code does not even compile. The addition function as coded returns a copy of a constructed instance of BigInt. For this a copy constructor must be defined. If I do not define it myself, then the compiler will do so. What does the compiler produce that I am not producing with the added copy constructor? Here's the compilation error I get:

$ g++ -std=c++14 -g mwe.cpp 
mwe.cpp: In function ‘BigInt operator+(const BigInt&, const BigInt&)’:
mwe.cpp:34:10: error: no matching function for call to ‘BigInt::BigInt(BigInt&)’
   return result;
          ^
mwe.cpp:9:3: note: candidate: BigInt::BigInt()
   BigInt() {}
   ^
mwe.cpp:9:3: note:   candidate expects 0 arguments, 1 provided
mwe.cpp: In function ‘int main()’:
mwe.cpp:44:23: error: no matching function for call to ‘BigInt::BigInt(BigInt)’
   BigInt mm_nn = mm + nn;
                       ^
mwe.cpp:9:3: note: candidate: BigInt::BigInt()
   BigInt() {}
   ^
mwe.cpp:9:3: note:   candidate expects 0 arguments, 1 provided

From it, it seems like the compiler expects a copy constructor that I have not provided. Now... IF I REMOVE the explicit keyword, everything works. However, I have seen implementations with explicit copy constructor, for example: Explicit copy constructor

What am I missing? Why can't I make this copy constructor explicit while overloading the addition operator? Should, in general, copy constructors be made explicit?

Eduardo
  • 697
  • 8
  • 26
  • Unrelated , you can simply make your parameterised cntor like this: `explicit BigInt(const std::string &in) :value_(in.rbegin(), in.rend()) { }` also there is a function in `` called `std::reverse`. – JeJo Jun 10 '18 at 18:31
  • in otherwords, you can write this function as: `std::string value() { return std::string(value_.rbegin(), value_.rend()); }` – JeJo Jun 10 '18 at 18:39
  • 1
    The problem is that your copy construct is `explicit`, so `return result;` does not work, you would need `return BigInt{result};`. Simply make your copy-constructor not explicit. – Holt Jun 10 '18 at 19:15
  • *"Why can't I make this copy constructor explicit"* - why would you *want* to make it explicit? There are finite, specific reasons one does so, but they're exceptional, not regular, and I see no evidence to support such an exceptional case here. You asked, *"What does the compiler produce that I am not producing*". In reality, you should be asking *"What am I producing that the compiler is not?"*, and the answer is a significantly more restrictive mechanism for invoking copy construction; specifically disqualifying all implicit copying. – WhozCraig Jun 10 '18 at 19:42
  • This code compiles correctly with `GCC 4.9.2`. What compiler are you using? – Piotr Siupa Jun 10 '18 at 21:33
  • Nvm, I didn't paste the line with copy constructor. – Piotr Siupa Jun 10 '18 at 21:38
  • Possible duplicate of [explicit copy constructor compile error](https://stackoverflow.com/questions/29472565/explicit-copy-constructor-compile-error) – Piotr Siupa Jun 10 '18 at 21:54
  • @NO_NAME that's a high possibility for this question. – JeJo Jun 10 '18 at 22:11
  • @JeJo Sorry, I don't understand. Could you rephrase? – Piotr Siupa Jun 10 '18 at 22:14
  • @NO_NAME The question you linked can be considered as a duplicate. That's what I meant :). BTW Thanks for that. – JeJo Jun 10 '18 at 22:16
  • @NO_NAME that compiler is nearly 4 years old – M.M Jun 10 '18 at 23:20
  • @M.M I just used `cpp.sh`. Anyway, it prints the same error, so it doesn't matter. – Piotr Siupa Jun 11 '18 at 07:53
  • The concept of "explicit" contradicts the concept of copy constructor. It doesn't make sense. – curiousguy Jun 13 '18 at 00:35

1 Answers1

4

Making the copy constructor explicit does not make sense. Remove it.

BigInt(const BigInt &in): value_(in.value_) {}

Conceptual Problem With explicit Copy Constructor

Making a copy constructor explicit makes returning an object from a function impossible.

Let's simplify your code to the following:

struct BigInt
{
   BigInt() {}
   explicit BigInt(const BigInt &in) {}
};

BigInt operator+(const BigInt &bi1, const BigInt &bi2)
{
   BigInt result;
   return result;
}

int main() {}

In the line return result, the compiler relies on the copy constructor to return an object. When the copy constructor is explicit, there is no way for a BigInt to be constructed as the return value.

Trying to use:

BigInt operator+(const BigInt &bi1, const BigInt &bi2)
{
   BigInt result;
   return BigInt(result);
}

is futile since that is equivalent to:

BigInt operator+(const BigInt &bi1, const BigInt &bi2)
{
   BigInt result;
   BigInt result1(result);
   return result1;
}

The problem continues to be there no matter what you do in the function.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • 1
    I agree. However, this is weird that compiler cannot find a match for `BigInt::BigInt(BigInt&)`. This error occurs even if you remove `const` from the constructor. – Piotr Siupa Jun 10 '18 at 21:49
  • OK, I get it now. `explicit` forbids compiler to call the constructor implicitly even if there is no type conversion. – Piotr Siupa Jun 10 '18 at 21:53
  • 2
    @R Sahu *Why can't I make this copy constructor explicit while overloading the addition operator?* You haven't answered this OPs question. – JeJo Jun 10 '18 at 22:09
  • @JeJo, added some more details. – R Sahu Jun 10 '18 at 22:39
  • @NO_NAME, the compiler tries to find a `BigInt(BigInt&)` since `BigInt(BigInt const&)` is explicit. – R Sahu Jun 10 '18 at 22:40
  • @RSahu Now, that deserves a UP :). Thanks for your explanation. Well, I actually found the code working with `return BigInt(result);`, when I use GCC 7.1 or later (see this: https://wandbox.org compiles), but older does not(see this: https://www.ideone.com/V20olX). However, I didn't get any explanation to understand this. How the later compilers overcome this problem! :( – JeJo Jun 10 '18 at 22:52
  • 1
    @JeJo, RVO might be at play there. IIRC, C++17 has added some clauses that make RVO mandatory in some constructs. – R Sahu Jun 10 '18 at 23:00
  • @JeJo, Return Value Optimization. If I Recall/Remember Correctly. – R Sahu Jun 10 '18 at 23:03
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/172859/discussion-between-jejo-and-r-sahu). – JeJo Jun 10 '18 at 23:05
  • 2
    Since C++17, if you `return` a prvalue then the prvalue initializes the result object, there are no temporaries. (When the function call initializes a variable, that variable is the result object). The code samples where you return a prvalue substantially differ from the samples where you return an lvalue. – M.M Jun 10 '18 at 23:30
  • @M.M, That does explain the behavior reported by JeJo. – R Sahu Jun 10 '18 at 23:47
  • @M.M Yes, that makes sense. – JeJo Jun 11 '18 at 06:58