This question already has an answer here:
Is a rvalue reference parameter that is returned by value an xvalue?
(1 answer)
Closed 11 months ago.
Related?: Why is the destructor called for an object that is not deleted?
As a minimal case, I have a templated heap-allocated move-only value class template. What I don't understand is why a function that just uses its noexcept move-constructor seems to want to use its d'tor:
#include <cassert>
#include <memory>
// Roughly a unique_ptr:
template <typename T>
class move_only_heap_value {
T* ptr;
public:
move_only_heap_value() : ptr(new T()) {}
move_only_heap_value(move_only_heap_value&& x) noexcept : ptr(x.ptr) { x.ptr = nullptr; }
// ...
T& operator*() { assert(ptr); return *ptr; }
const T& operator*() const { assert(ptr); return *ptr; }
// ...
~move_only_heap_value() {
if (ptr) {
delete ptr;
}
}
};
//struct S { S(); ~S(); }; // I don't see ~S() called anywhere.
struct S;
move_only_heap_value<S> foo(move_only_heap_value<S>&& x) {
return std::move(x); // Error here due to missing ~move_only_heap_value<S>()
}
produces
<source>: In instantiation of 'move_only_heap_value<T>::~move_only_heap_value() [with T = S]':
<source>:27:21: required from here
<source>:18:13: error: possible problem detected in invocation of 'operator delete' [-Werror=delete-incomplete]
18 | delete ptr;
| ^~~~~~~~~~
<source>:18:20: error: invalid use of incomplete type 'struct S' [-Werror]
18 | delete ptr;
| ^~~
<source>:24:8: note: forward declaration of 'struct S'
24 | struct S;
| ^
<source>:18:13: note: neither the destructor nor the class-specific 'operator delete' will be called, even if they are declared when the class is defined
18 | delete ptr;
| ^~~~~~~~~~
cc1plus: all warnings being treated as errors
ASM generation compiler returned: 1
<source>: In instantiation of 'move_only_heap_value<T>::~move_only_heap_value() [with T = S]':
<source>:27:21: required from here
<source>:18:13: error: possible problem detected in invocation of 'operator delete' [-Werror=delete-incomplete]
18 | delete ptr;
| ^~~~~~~~~~
<source>:18:20: error: invalid use of incomplete type 'struct S' [-Werror]
18 | delete ptr;
| ^~~
<source>:24:8: note: forward declaration of 'struct S'
24 | struct S;
| ^
<source>:18:13: note: neither the destructor nor the class-specific 'operator delete' will be called, even if they are declared when the class is defined
18 | delete ptr;
| ^~~~~~~~~~
cc1plus: all warnings being treated as errors
Execution build compiler returned: 1
https://godbolt.org/z/KoMrcM1n4
I understand why this translation unit can't call ~move_only_heap_value<S>() (because S is incomplete), but what possess it to want to call that? If I instead define S but leave its c'tor and d'tor undefined, I get a link error as expected but I don't see the d'tor called in the assembly, so why does it think it needs to instantiate it?
A simpler case, but possibly subtly different:
#include <utility>
struct Indestructible {
Indestructible() = default;
Indestructible(Indestructible&& x) noexcept = default;
~Indestructible() = delete;
};
Indestructible foo(Indestructible&& x) {
return std::move(x);
}
produces
<source>: In function 'Indestructible foo(Indestructible&&)':
<source>:10:21: error: use of deleted function 'Indestructible::~Indestructible()'
10 | return std::move(x);
https://godbolt.org/z/MbKhqddxd
but I worry that's different since maybe the returned result has automatic storage duration and so has to be destructed at some sequence point in the future but is not destructible, whereas the original version can be destructed, just not in this translation unit.
but what possess it to want to call that?
You delete a pointer to an object of that type here.
~move_only_heap_value() {
if (ptr) {
delete ptr;
// ^^^^^^^^^^
}
}
That invokes the destructor, and the type must be defined at that point. ~move_only_heap_value of the template in turn has been instantiated because you have defined a function that returns an object of that template instance.
You can remove the definition of this destructor, and it will compile. You can define it elsewhere in a place where S is defined.
but I don't see the d'tor called in the assembly
Just because something won't end up in the assembly, doesn't necessarily mean that it won't need to be well-defined.
P.S. if (ptr) delete ptr; can always be simplified into delete ptr; The condition is redundant.
Related
Consider the following code:
#include <iostream>
#include <thread>
int main() {
std::thread t;
const auto l = [x = std::move(t)]{};
decltype(l) m = std::move(l);
}
This code doesn't compile with the following messages:
prog.cc: In function 'int main()':
prog.cc:7:32: error: use of deleted function 'main()::<lambda()>::<lambda>(const main()::<lambda()>&)'
7 | decltype(l) m = std::move(l);
| ^
prog.cc:6:37: note: 'main()::<lambda()>::<lambda>(const main()::<lambda()>&)' is implicitly deleted because the default definition would be ill-formed:
6 | const auto l = [x = std::move(t)]{};
| ^
prog.cc:6:37: error: use of deleted function 'std::thread::thread(const std::thread&)'
In file included from prog.cc:2:
/opt/wandbox/gcc-head/include/c++/10.0.1/thread:154:5: note: declared here
154 | thread(const thread&) = delete;
| ^~~~~~
Is there a way to make lambda non-copyable or non-movable without explicit capturing an any non-copyable variable (i.e. leaving [] empty)?
You can write a simple enveloping aggregate that will prevent the move and copy.
struct NoCopyMove {
NoCopyMove(NoCopyMove const&) = delete;
NoCopyMove(NoCopyMove&&) = delete;
void operator=(NoCopyMove const&) = delete;
void operator=(NoCopyMove&&) = delete;
};
template<class Functor>
struct Fixed : Functor, NoCopyMove {
using Functor::operator();
};
template<typename F>
Fixed (F&&) -> Fixed<std::decay_t<F>>;
To be used like this
const auto l = Fixed{[]{}};
NoCopyMove is a simple mixin that disables copying and moving. Writing it this way allows us to keep the specializations of Fixed as simple aggregates.
All Fixed does is inherit/initialize as base (with guaranteed copy elision when possible) the functor it's being given as an argument. And then expose its operator().
Since there are no members involved (other then maybe in Functor), and since a lambda cannot possibly inherit from our custom NoCopyMove class, the empty base optimization kicks in for stateless lambdas. So objects are not expected to be any larger than the lambda you initialize them with.
I'm trying to set one of the elements of an array to be a different object. But, the compiler is deleting the = operator. Why is it doing that here, exactly? And how can I work around it?
Example code:
struct IntContainer
{
IntContainer(const int value) :
value(value)
{
}
IntContainer() :
IntContainer(0)
{
}
const int value;
};
int main(int argc, char** argv)
{
IntContainer intContainers[3];
IntContainer newIntContainer(420);
intContainers[0] = newIntContainer; // <-- Causes compiler error
return 0;
}
The compiler error I'm getting when compiling this snippet is:
main.cpp: In function 'int main(int, char**)':
main.cpp:23:24: error: use of deleted function 'IntContainer& IntContainer::operator=(const IntContainer&)'
intContainers[0] = newIntContainer; // <-- Causes compiler error:
^~~~~~~~~~~~~~~
main.cpp:2:8: note: 'IntContainer& IntContainer::operator=(const IntContainer&)' is implicitly deleted because the default definition would be ill-formed:
struct IntContainer
^~~~~~~~~~~~
main.cpp:2:8: error: non-static const member 'const int IntContainer::value', can't use default assignment operator
The compiler generally gives you operator= and the copy constructor for free, but when a class contains a const member, it makes no sense to generate operator= because you can't perform an assignment to the const member.
You could write your own, but you still wouldn't be able to assign values to the const member.
Suppose I have the following (very rough) code
class foo
{
public:
int a;
foo() //Regular constructor
{
......
std::cout << "Regular \n";
}
foo(foo& f) //Copy constructor with with non-const parameter
{
....
std::cout << "Copy Constructor \n" ;
}
foo& operator= (const foo& f)
{
std::cout << "Copy Assignment Operator \n" ;
return *this;
}
};
foo makeFoo()
{
return foo();
}
int main()
{
foo a = makeFoo();
}
Now when attempting to simulate this code with the -fno-elide-constructors (for experimental and educational purpose) I get the following errors
main.cpp: In function 'foo makeFoo()':
main.cpp:32:15: error: no matching function for call to 'foo::foo(foo)'
return foo();
^
main.cpp:32:15: note: candidates are:
main.cpp:12:5: note: foo::foo(foo&)
foo( foo& f)
^
main.cpp:12:5: note: no known conversion for argument 1 from 'foo' to 'foo&'
main.cpp:10:5: note: foo::foo()
foo() { std::cout << "Regular \n";}
^
main.cpp:10:5: note: candidate expects 0 arguments, 1 provided
main.cpp: In function 'int main()':
main.cpp:40:20: error: no matching function for call to 'foo::foo(foo)'
foo a = makeFoo(); //Move constrcutor - Move Constructor or with copy elision only regular constrcutor
^
main.cpp:40:20: note: candidates are:
main.cpp:12:5: note: foo::foo(foo&)
foo( foo& f)
^
main.cpp:12:5: note: no known conversion for argument 1 from 'foo' to 'foo&'
main.cpp:10:5: note: foo::foo()
foo() { std::cout << "Regular \n";}
^
main.cpp:10:5: note: candidate expects 0 arguments, 1 provided
main.cpp: In function 'foo makeFoo()':
main.cpp:33:1: warning: control reaches end of non-void function [-Wreturn-type]
}
^
Now these errors are resolved by making the parameter of the copy constructor a const type. I wanted to know if my understanding of why making the parameter of the copy constructor a const type fixes this problem. Please let me know if my understanding or reasoning is incorrect. Now in the method makeFoo after the execution of the statement return foo(); regular constructor of foo is called. Then the copy constructor of foo is called. Now in my code I have something like this:
foo a = makeFoo();
In this case the copy constructor of the foo a is called. since the return type of makeFoo() is an rvalue which is always a constant and the copy constructor of foo is a non-const thus an error is generated as we are attempting to pass a constant reference type to a non-constant reference type.
As you mentioned, in your expression foo a = makeFoo(); makeFoo() is an rvalue.
Constructor with non-const input parameter (your case) can only bind lvalue. In fact, makeFoo() returns a temporary objects created by the compiler, and the compiler doesn't want you to modify this temporary object that can be deleted at any moment. So the compiler will throw an error when you try to pass an rvalue to a constructor with non-const input parameter.
Constructor with const input parameter can bind rvalue and lvalue.
8.3.5/8 Functions [dcl.fct] says
[...] Functions shall not have a return type of
type array or function, although they may have a return type of type pointer or reference to such things. [...]
Why so explicit of a rule? Is there some syntax that would even allow returning a function as opposed to a function pointer?
Am I miss-interpreting the quote?
typedef void (*fp)();
void foo(){}
fp goo()
{
return foo; //automatically converted to function pointer
}
This is quite a contrived example of a function trying to return a function:
void foo() { }
template<typename T>
T f() { return foo; }
int main(){
f<decltype(foo)>();
}
This is the error I get from Clang 3.2:
Compilation finished with errors:
source.cpp:7:5: error: no matching function for call to 'f'
f<decltype(foo)>();
^~~~~~~~~~~~~~~~
source.cpp:4:3: note: candidate template ignored: substitution failure
[with T = void ()]: function cannot return function type 'void ()'
T f() { return foo; }
~ ^
1 error generated.
Is there some syntax that would even allow returning a function as opposed to a function pointer?
A syntax? Sure there is:
using fun = int (int);
fun function_that_returns_a_function();
That doesn’t compile because the rule in §8.3.5/8 forbids it. I don’t know why the rule specifically exists – but consider that the type “function” doesn’t have any size so you cannot create objects of function type in C++.
I know this probably does not answer your question completely but it does so partially
You can return a function from another function (that's what lambdas are)
std::function<int (int)> retLambda() {
return [](int x) { return x; };
}
I am looking to create a type which can be initialized with assignment from another type, but cannot be copied. The idea is similar to that of a scoped smart pointer in that I want the object of this type to own a resource for its lifetime, but I also want to be able to use assignment syntax. So in synopsis, this is what I want:
T x = new U; // allowed
T y(new U); // allowed
T z = x; // not allowed
T x2(x) // not allowed
This is what I have tried so far...
#include <boost/noncopyable.hpp>
class U {};
class T : boost::noncopyable {
public:
T(U *p) : p_(p) {
}
~T() {
delete p_;
}
operator bool() const { return p_ != 0; }
private:
U *p_;
};
int main() {
T x = new U; // allowed
T y(new U); // allowed
//T z = x; // not allowed
//T x2(x) // not allowed
}
which unfortunately results in an error:
$ g++ test.cc -o test /usr/include/boost/noncopyable.hpp: In copy
constructor 'T::T(const T&)': /usr/include/boost/noncopyable.hpp:27:7:
error: 'boost::noncopyable_::noncopyable::noncopyable(const
boost::noncopyable_::noncopyable&)' is private test.cc:6:30: error:
within this context test.cc: In function 'int main()': test.cc:20:12:
note: synthesized method 'T::T(const T&)' first required here
NOTE: C++11's move features are a non-option for me, as this must be able to be compiled with a relatively old version of gcc that does not have C++11 support.
Because of the lack of C++11 support, I am not sure there is a "good" solution. But I figured I'd ask.
If my understanding from comments is correct - you want something like that:
U* create_T() { return new U; }
if (T t = create_T())
{
// do something with t
}
The problem here, already mentioned in comments: this syntax T t = u is just calling copy constructor. If u is of type T it is equivalent to: T t(u). If u, like in your example, is of another type convertible to T (here by T::T(U*)) then it is actually this: T t(T(u)). So here you have the copy contructor for which compiler complains.
There is no solution, because this is not valid if syntax:
if (T i(create_T())) {}
However I would not write all of these without good advise ;)
You can forget about problems with copy constructor with this syntax:
if (U* u = create_T()) {
T t(u);
....
}
BTW, std::auto_ptr has the same problem as your T type. Just add explicit to your T(U*) constructor and you will see similarity to auto_ptr:
class U {};
class T : boost::noncopyable {
public:
explicit T(U *p) : p_(p) {
}
~T() {
delete p_;
}
private:
U *p_;
};
int main() {
T x = new U; // allowed
T y(new U); // allowed
std::auto_ptr<U> a = new U;
std::auto_ptr<U> b(new U);
}
Results in:
prog.cpp:25: error: conversion from ‘U*’ to non-scalar type ‘T’ requested
prog.cpp:27: error: conversion from ‘U*’ to non-scalar type ‘std::auto_ptr<U>’ requested
Here my ideone study...