4

I have this code:

#include <string>

class A {
 public:
//    A(A const &) = delete;   // Code fails if this is uncommented.
    explicit A(int);
    explicit A(::std::string const &);

 private:
    ::std::string myname_;
    int foo_;
};

static constexpr bool which = false;

A test(::std::string const &s, int a)
{
    if constexpr (which) {
        A x{a};
        return x;
    } else {
        A y{s};
        return y;
    }
}

This code fails if A has a deleted copy constructor. But, given the rules about return type for functions with if constexpr in them, it seems like the compiler should be applying RVO here.

Is there a reason why it isn't, other than it being an overlooked case in the language specification?

Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • On what compiler does this "fail"? – aschepler Feb 26 '20 at 01:32
  • @aschepler - It fails if you uncomment out the deletion of the move constructor. And it does on both gcc and clang. – Omnifarious Feb 26 '20 at 01:34
  • 2
    `if constexpr` only discards code in a template instantiation. In non-template code both branches are retained. See this [related question](https://stackoverflow.com/questions/53585634/is-if-constexpr-useful-outside-of-templates) or this [one](https://stackoverflow.com/questions/58300760/wrong-understanding-of-if-constexpr). – 1201ProgramAlarm Feb 26 '20 at 01:35
  • @1201ProgramAlarm - That's very interesting to know. Thank you. – Omnifarious Feb 26 '20 at 01:39

2 Answers2

3

This has nothing to do with if constexpr

Simply this code is not allowed to compile:

class A {
 public:
    A(A const &) = delete;
    explicit A(int);
};

A test(int a)
{
    A x{a};
    return x; // <-- error call to a deleted constructor `A(A const &) = delete;`
}

The changes in C++17 you are thinking of have to do with temporary materialization and don't apply to NRVO because x is not a prvalue.

For instance this code was illegal pre C++17 and now it is allowed:

A test(int a)
{
    return A{a}; // legal since C++17
}
bolov
  • 72,283
  • 15
  • 145
  • 224
  • Ahh, that does indeed answer my question. Interesting. I was confused about how the mandatory optimization worked. – Omnifarious Feb 26 '20 at 01:38
  • I don't understand why `A x{a};` is illegal – Tas Feb 26 '20 at 01:42
  • The other thing going on: It would be legal if `A` had a non-deleted and public move constructor, even though the expression is an lvalue. But declaring a deleted copy constructor means there is no move constructor at all. – aschepler Feb 26 '20 at 01:42
1

This is NRVO, which is non-mandatory copy elision:

(emphasis mine)

  • In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type. This variant of copy elision is known as NRVO, "named return value optimization".

This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:


BTW: Note that in your code, both the if part and else part of the constexpr if statement will be checked.

Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive:

void f() {
    if constexpr(false) {
        int i = 0;
        int *p = i; // Error even though in discarded statement
    }
}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • This is a nice answer, but the other answer is a more direct answer to my question. Had I seen this one first, I would've started an argument about that not being true in C++17. :-) I would've been wrong, but the path leading to me realizing that would've been a lot longer. – Omnifarious Feb 26 '20 at 01:53