Consider the following code:
#include <variant>
#include <cassert>
struct foo {
foo() noexcept;
foo(const foo&) noexcept = default;
foo(foo&&) noexcept = default;
foo& operator=(const foo&) noexcept = default;
foo& operator=(foo&&) noexcept = default;
};
std::variant<std::monostate, foo> var;
foo::foo() noexcept {
assert(!var.valueless_by_exception());
};
int main() {
var.emplace<foo>();
}
With libstdc++ (from GCC 11), this works, but with libc++ (from LLVM 12), and MSVC, the assert fails.
Which standard library implements the correct behaviour ? At no point any exception is thrown, and my type is entirely noexcept, so I'd expect "valueless_from_exception" to never be true.
To quote the standard (https://timsong-cpp.github.io/cppwp/n4861/variant#status):
A variant might not hold a value if an exception is thrown during a type-changing assignment or emplacement.
here I am clearly not in that case.
The standard doesn't currently provide an answer to your question, but the direction that LWG appears to be moving in is that your code will have undefined behaviour.
Related
I came around a strange error with MSVC compiler.
Let's say that I have class Foo, that must not be constructed from anything but uint32_t (copy, move and default = ok).
I also have a class Bar, that defines a Foo conversion operator.
#include <cstdint>
struct Foo
{
Foo() = default;
constexpr Foo(const Foo&) = default;
constexpr Foo(Foo&&) = default;
// may be constructed only from uint32
constexpr Foo(uint32_t word)
{}
// This expression is required to
// prevent construction from types that are
// not uint32_t or copy or move
// See 9.5.3 Deleted definitions for an example of
// this specific use case.
template <typename T>
constexpr Foo(T) = delete;
};
struct Bar
{
constexpr operator Foo () const { return uint32_t(); }
};
int main()
{
constexpr Foo foo = Bar();
return 0;
}
This compiles fine with MinGW, but MSVC gives an error:
main.cpp(27): error C2280: 'Foo::Foo<Bar>(T)': attempting to reference a deleted function`
Is this behavior defined by the standard? Or is it a MSVC bug? Or maybe MinGW does something wrong?
The error can be eradicated by declaring the delete'd template constructor as explicit.
I'm trying to return a custom type from a Concurrency Runtime task. My custom type should be constructible through a static factory method only (aside from being move-constructible), e.g.:
#include <utility>
struct foo
{
foo() = delete;
foo(foo const&) noexcept = delete;
foo& operator=(foo const&) noexcept = delete;
foo(foo&&) noexcept = default;
foo& operator=(foo&&) noexcept = default;
static foo make_foo(int const value) noexcept { return std::move(foo{ value }); }
private:
foo(int const) noexcept {}
};
And a simple test case:
#include <ppltasks.h>
using namespace concurrency;
int main()
{
auto&& task
{
create_task([value = 42]()
{
return foo::make_foo(value);
})
};
auto&& result{ task.get() };
}
This, however, fails to compile, producing the following (abridged) compiler diagnostic:
ppltasks.h(644,1): error C2280: 'foo::foo(const foo &) noexcept': attempting to reference a deleted function
main.cpp(223): message : see declaration of 'foo::foo'
main.cpp(223,5): message : 'foo::foo(const foo &) noexcept': function was explicitly deleted
No surprises here, the copy-c'tor is indeed explicitly deleted, on purpose. I just don't understand, why the move-constructor isn't considered as a candidate.
It seems that I can get the code to compile, as long as I provide a default-c'tor, a copy-c'tor, and a copy-assignment operator. Is this a limitation of the library (ConcRT), the compiler (Visual Studio 2019 16.1.0), the programming language, or my control over it?
Phrased another way: Is there anything I can do to get my custom type (as it is) to play along with concurrency::task, or are default-constructibility, copy-constructibility, and copy-assignability undocumented requirements?
Consider the following simple move-only class:
struct bar {
constexpr bar() = default;
bar(bar const&) = delete;
bar(bar&&) = default;
bar& operator=(bar const&) = delete;
bar& operator=(bar&&) = default;
};
Now, let's create a wrapper:
template <class T>
struct box {
constexpr box(T&& x)
: _payload{std::move(x)}
{}
constexpr explicit operator T() &&
{
return std::move(_payload);
}
private:
T _payload;
};
And a test:
int main()
{
auto x = box<bar>{bar{}};
auto y = static_cast<bar&&>(std::move(x));
}
Clang-6.0 is happy with this code if compiled with -std=c++14 or above. GCC-8.1 however produces the following error:
<source>: In function 'int main()':
<source>:29:45: error: invalid static_cast from type 'std::remove_reference<box<bar>&>::type' {aka 'box<bar>'} to type 'bar&&'
auto y = static_cast<bar&&>(std::move(x));
Here's a link to compiler explorer to try it out.
So my question is, who's right and who's wrong?
Simplified example:
struct bar
{
bar() = default;
bar(bar const&) = delete;
bar(bar&&) = default;
};
struct box
{
explicit operator bar() && { return bar{}; }
};
int main() { static_cast<bar&&>(box{}); }
live on godbolt.org
First of all, let's see what it means for a conversion operator to be explicit:
A conversion function may be explicit, in which case it is only considered as a user-defined conversion for direct-initialization. Otherwise, user-defined conversions are not restricted to use in assignments and initializations.
Is static_cast considered direct-initialization?
The initialization that occurs in the forms
T x(a);
T x{a};
as well as in new expressions, static_cast expressions, functional notation type conversions, mem-initializers, and the braced-init-list form of a condition is called direct-initialization.
Since static_cast is direct-initalization, it doesn't matter whether the conversion operator is marked explicit or not. However, removing explicit makes the code compile on both g++ and clang++: live example on godbolt.org.
This makes me believe that it's a g++ bug, as explicit makes a difference here... when it shouldn't.
program:
#include <stdio.h>
struct bar_t {
int value;
template<typename T>
bar_t (const T& t) : value { t } {}
// edit: You can uncomment these if your compiler supports
// guaranteed copy elision (c++17). Either way, it
// doesn't affect the output.
// bar_t () = delete;
// bar_t (bar_t&&) = delete;
// bar_t (const bar_t&) = delete;
// bar_t& operator = (bar_t&&) = delete;
// bar_t& operator = (const bar_t&) = delete;
};
struct foo_t {
operator int () const { return 1; }
operator bar_t () const { return 2; }
};
int main ()
{
foo_t foo {};
bar_t a { foo };
bar_t b = static_cast<bar_t>(foo);
printf("%d,%d\n", a.value, b.value);
}
output for gcc 7/8:
2,2
output for clang 4/5 (also for gcc 6.3)
1,1
It seems that the following is happening when creating the instances of bar_t:
For gcc, it calls foo_t::operator bar_t then constructs bar_t with T = int.
For clang, it constructs bar_t with T = foo_t then calls foo_t::operator int
Which compiler is correct here? (or maybe they are both correct if this is some form of undefined behaviour)
I believe clang's result is correct.
In both bar_t a { foo } direct-list-initialization and in a static_cast between user defined types, constructors of the destination type are considered before user defined conversion operators on the source type (C++14 [dcl.init.list]/3 [expr.static.cast]/4). If overload resolution finds a suitable constructor then it's used.
When doing overload resolution bar_t::bar_t<foo_t>(const foo_t&) is viable and will be a better match than one to any instantiation of this template resulting in the use of the cast operators on foo. It will also be better than any default declared constructors since they take something other than foo_t, so bar_t::bar_t<foo_t> is used.
The code as currently written depends on C++17 guaranteed copy elision; If you compile without C++17's guaranteed copy elision (e.g. -std=c++14) then clang does reject this code due to the copy-initialization in bar_t b = static_cast<bar_t>(foo);.
#include <deque>
#include <vector>
struct A
{
A(int* const p) : m_P(p) {}
A(A&& rhs) : m_P(rhs.m_P) { rhs.m_P = nullptr; }
A& operator=(A&& rhs) { delete m_P; m_P = rhs.m_P; rhs.m_P = nullptr; }
~A() { delete m_P; }
A(A const& rhs) = delete;
A& operator=(A const& rhs) = delete;
int* m_P;
};
int main()
{
#ifdef DDDEQUE
std::vector<std::pair<int, std::deque<A> > > vd;
vd.emplace(vd.end(), 1, std::deque<A>());
#endif // #ifdef DDDEQUE
std::vector<std::pair<int, std::vector<A> > > vv;
vv.emplace(vv.end(), 1, std::vector<A>());
}
If compiling with g++ 4.8.5, 5.2.0, 5.3.0 and -DDDDEQUE I get a verbose error message ending with
.../bits/stl_construct.h:75:7: error: use of deleted function ‘A::A(const A&)’
{ ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
^
gcc.cpp:11:5: note: declared here
A(A const& rhs) = delete;
without -D... compiles OK.
With VC2015, VC2012 both versions compile OK.
Does deque (but not vector) need the copy constructor for gcc?
This appears to be specific to libstdc++ (gcc); given the code below;
struct A
{
A() {};
A(A&&) noexcept { }
A& operator=(A&&) noexcept { return *this; }
~A() { }
};
int main()
{
std::vector<A> a;
a.push_back(A{}); // or emplace(a.end()... etc.
std::vector<std::deque<A>> b;
b.push_back(std::deque<A>());
std::vector<std::pair<int,A>> c;
c.push_back(std::pair<int,A>{});
std::vector<std::pair<int,std::deque<A>>> d;
d.push_back(std::pair<int,std::deque<A>>{});
}
G++ fails to compile b, and d, clang compiles all 4 (except possibly d depending on the version of libc++ used) and MSVC compiles all 4 cases (using their own associated standard library; with libstdc++, clang also fails b and d).
Does deque (but not vector) need the copy constructor for gcc?
It appears as though, yes gcc does still require the copy constructor.
In more formal terms; in C++03 std::deque required the type used in the container to be Copy Constructible and Copy Assignable. This changed in C++11, the requirements were relaxed, albeit a complete type is generally still required - given the OP sample, your standard library still requires the copy construction and assignment.
From the linked reference;
T - The type of the elements.
T must meet the requirements of CopyAssignable and CopyConstructible. (until C++11)
The requirements that are imposed on the elements depend on the actual operations performed on the container. Generally, it is required that element type is a complete type and meets the requirements of Erasable, but many member functions impose stricter requirements. (since C++11)