c++move-semanticscomplex-data-types

How to ensure elements of a returned array aren't copied?


In the following code fragment (I'm experimenting with it to understand how modern C++ is different):

auto input(auto p) {
  std::cout << p << ' ';
  long long i;
  return
    std::cin >> i
    && std::set<int>{EOF, '\n'}.contains(std::cin.get())
    && i >= 0
    ? std::optional{i}
    : std::nullopt;
}

const auto input2() {
  auto a = input("Enter the first operand:");
  if (a) {
    auto b = input("Enter the second operand:");
    if (b) return std::optional{std::pair{a.value(), b.value()}};
  }
  return decltype(input2()){};
}

void reqNat() {
    std::cout << "Operands must be natural numbers!";
}

void binary(auto f, char op) {
  auto xy = input2();
  if (xy) {
    auto &[x, y] = xy.value();
    std::cout << std::format("{} {} {} = {}", x, op, y, f(x, y));
  }
  else reqNat(); 
}

are x and y subobjects of xy? If so, is the const in the return type of input2 required to ensure it? What does the const change? Would it be good to put it on input too?


Solution

  • are x and y subobjects of xy?

    Yes, in:

    auto &[x, y] = xy.value();
    

    ... xy.value() returns an lvalue reference to the object inside, and the reference on the left hand side binds to it. x and y are then going to be lvalues which name subobjects inside of xy.

    If so, is the const in the return type of input2 required to ensure it?

    No, and const has nothing to do with this. In:

    const auto input2() { /* ... */ }
    
    // ...
    
    auto xy = input2();
    
    // note: this assertion would pass, because xy is not const
    static_assert(!std::is_const_v<decltype(xy)>);
    

    ... the const is harmful, because we are declaring an object with auto, not const auto. This means we are unnecessarily calling the copy constructor of std::optional<std::pair<...>> to turn the returned const object into a non-const object.

    In general, it is not recommended to put const on return types, because:

    What does the const change? Would it be good to put it on input too?

    It only makes us call the copy constructor unnecessarily. The following use of structured bindings works regardless of whether input2() returns a const or non-const type.

    We should not put on input, we should remove const from input2.

    Stylistic Issues

    Note that there are quite a few things in your code that aren't "good modern C++":

    // Constructing a std::set just to check if an int is one of two values is excessive.
    std::set<int>{EOF, '\n'}.contains(std::cin.get())
    // Consider using something more lightweight, like an IILE
    [c = std::cin.get()] { return c == EOF || c == '\n' }()
    // alternatively, make the set 'static const' at least
    
    
    // Excessive use of deduced return types.
    // The function name and parameters don't tell us anything about the return
    // type being std::optional<long long>.
    // Also, unnecessary use of function templates.
    // You could have just accepted a std::string_view parameter.
    auto input(auto p) { /* ... */ }
    
    
    // If you have to do this, just use an explicit return type.
    // This line is very surprising and could have been return std::nullopt; with
    // an explicit type.
    return decltype(input2()){};