Question Details

No question body available.

Tags

c++ language-lawyer

Answers (2)

July 7, 2026 Score: 6 Rep: 230,872 Quality: High Completeness: 80%

According to variadicarguments, emphasis mine:

Non-POD class types (until C++11) Scoped enumerations and class types with an eligible non-trivial copy constructor, an eligible non-trivial move constructor, or a non-trivial destructor (since C++11) are conditionally-supported in potentially-evaluated calls with implementation-defined semantics (these types are always supported in unevaluated calls).

And decltype is an unevaluated context.

auto would use (deleted) move-constructor for NoMove&&.

If DeclVal() returned a prvalue, auto would NOT use move constructor for NoMove, thanks to mandatory copy elision (since C++17).

July 7, 2026 Score: 1 Rep: 46,242 Quality: Medium Completeness: 80%

using Type2 = decltype(F2(DeclVal())); will have to initialize the first parameter of F2(NoMove) to call it, requiring the move constructor.

When calling a function with ellipses, [expr.call]:

  1. A function can be declared to accept fewer arguments (by declaring default arguments) or more arguments (by using the ellipsis, ..., or a function parameter pack ([dcl.fct])) than the number of parameters in the function definition.
    [Note 10: This implies that, except where the ellipsis (...) or a function parameter pack is used, a parameter is available for each argument. — end note]
  2. When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg ([support.runtime]).
  3. The lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard conversions are performed on the argument expression.

The lvalue-to-rvalue conversion is what usually requires the move constructor to be called.

In C++11 [conv.lval]:

When an lvalue-to-rvalue conversion occurs in an unevaluated operand or a subexpression thereof (Clause [expr]) the value contained in the referenced object is not accessed. Otherwise, if the glvalue has a class type, the conversion copy-initializes a temporary of type T from the glvalue and the result of the conversion is a prvalue for the temporary.

So when unevaluated, the 'Otherwise, ...' part doesn't apply and no temporary is copy-initialized (so no move constructor is needed).

C++14 [conv.lval] has wording to a similar effect, 'If e is not potentially evaluated, the value is not accessed, and only in all other cases the temporary is copy-initialized'.

This changed in C++17 P0135R1 ('guaranteed copy elision'). The 'in all other cases' is struck, so the temporary is unconditionally copy-initialized from the class type object, so this should be invalid in C++17 mode.

Clang bug appears here: https://github.com/llvm/llvm-project/blob/0ac35389e9d47fb9aa2934cbfab274aa69d05317/clang/lib/Sema/SemaExpr.cpp#L929-L940 relying on the C++11 wording