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);.
Clang 3.2 reports an error in the following code, and I do not understand why there is a problem. The error occurs only in the template function, and only if braces are used for initialization. The other two initializations work as expected.
struct foo {
foo() { }
~foo() = default;
// deleted
foo(const foo& rhs) = delete;
foo(foo&& rhs) noexcept = delete;
auto operator=(const foo& rhs) -> foo& = delete;
auto operator=(foo&& rhs) noexcept -> foo& = delete;
};
template <typename Type>
void bar() {
foo a; // OK
foo b{}; // ERROR
}
int main() {
foo c{}; // OK
bar<int>();
}
If I compile the code with clang++ -Wall -std=c++11 -c, Clang prints the following error message:
bug.cpp:14:9: error: conversion function from 'foo' to 'foo' invokes a deleted function
foo b{}; // ERROR
^
bug.cpp:19:5: note: in instantiation of function template specialization 'bar<int>' requested
here
bar<int>();
^
bug.cpp:6:5: note: function has been explicitly marked deleted here
foo(foo&& rhs) noexcept = delete;
^
1 error generated.
I have no idea why Clang tries to do a conversion. It sounds like a bug. Unfortunately, I have the problem in a more complex code base, where the solution is not as easy as just removing the braces.
Why does Clang need a conversion in this case? And how can I get it working in general?
This is definitely a bug. There is no reason why an attempt should be made to invoke a move constructor, since you have a default initialization:
foo b{}; // Same as "foo b;" in any case
If copy initialization were involved, things would be different, but that is not the case.
Besides, your code compiles fine on GCC 4.7.2.
class my_class
{
...
my_class(my_class const &) = delete;
...
};
What does = delete mean in that context?
Are there any other "modifiers" (other than = 0 and = delete)?
Deleting a function is a C++11 feature:
The common idiom of "prohibiting copying" can now be expressed
directly:
class X {
// ...
X& operator=(const X&) = delete; // Disallow copying
X(const X&) = delete;
};
[...]
The "delete" mechanism can be used for any function. For example, we
can eliminate an undesired conversion like this:
struct Z {
// ...
Z(long long); // can initialize with a long long
Z(long) = delete; // but not anything less
};
= 0 means that a function is pure virtual and you cannot instantiate an object from this class. You need to derive from it and implement this method
= delete means that the compiler will not generate those constructors for you. AFAIK this is only allowed on copy constructor and assignment operator. But I am not too good at the upcoming standard.
This excerpt from The C++ Programming Language [4th Edition] - Bjarne Stroustrup book talks about the real purpose behind using =delete:
3.3.4 Suppressing Operations
Using the default copy or move for a class in a hierarchy is typically
a disaster: given only a pointer to a base, we simply don’t know what
members the derived class has, so we can’t know how to copy
them. So, the best thing to do is usually to delete the default copy
and move operations, that is, to eliminate the default definitions of
those two operations:
class Shape {
public:
Shape(const Shape&) =delete; // no copy operations
Shape& operator=(const Shape&) =delete;
Shape(Shape&&) =delete; // no move operations
Shape& operator=(Shape&&) =delete;
˜Shape();
// ...
};
Now an attempt to copy a Shape will be caught by the compiler.
The =delete mechanism is general, that is, it can be used to suppress any operation
Are there any other "modifiers" (other than = 0 and = delete)?
Since it appears no one else answered this question, I should mention that there is also =default.
https://learn.microsoft.com/en-us/cpp/cpp/explicitly-defaulted-and-deleted-functions#explicitly-defaulted-functions
The coding standards I've worked with have had the following for most of class declarations.
// coding standard: disallow when not used
T(void) = delete; // default ctor (1)
~T(void) = delete; // default dtor (2)
T(const T&) = delete; // copy ctor (3)
T(const T&&) = delete; // move ctor (4)
T& operator= (const T&) = delete; // copy assignment (5)
T& operator= (const T&&) = delete; // move assignment (6)
If you use any of these 6, you simply comment out the corresponding line.
Example: class FizzBus require only dtor, and thus do not use the other 5.
// coding standard: disallow when not used
FizzBuzz(void) = delete; // default ctor (1)
// ~FizzBuzz(void); // dtor (2)
FizzBuzz(const FizzBuzz&) = delete; // copy ctor (3)
FizzBuzz& operator= (const FizzBuzz&) = delete; // copy assig (4)
FizzBuzz(const FizzBuzz&&) = delete; // move ctor (5)
FizzBuzz& operator= (const FizzBuzz&&) = delete; // move assign (6)
We comment out only 1 here, and install the implementation of it else where (probably where the coding standard suggests). The other 5 (of 6) are disallowed with delete.
You can also use '= delete' to disallow implicit promotions of different sized values ... example
// disallow implicit promotions
template <class T> operator T(void) = delete;
template <class T> Vuint64& operator= (const T) = delete;
template <class T> Vuint64& operator|= (const T) = delete;
template <class T> Vuint64& operator&= (const T) = delete;
A deleted function is implicitly inline
(Addendum to existing answers)
... And a deleted function shall be the first declaration of the function (except for deleting explicit specializations of function templates - deletion should be at the first declaration of the specialization), meaning you cannot declare a function and later delete it, say, at its definition local to a translation unit.
Citing [dcl.fct.def.delete]/4:
A deleted function is implicitly inline. ( Note: The one-definition
rule
([basic.def.odr])
applies to deleted definitions. — end note ] A deleted definition
of a function shall be the first declaration of the function or, for
an explicit specialization of a function template, the first
declaration of that specialization. [ Example:
struct sometype {
sometype();
};
sometype::sometype() = delete; // ill-formed; not first declaration
— end example )
A primary function template with a deleted definition can be specialized
Albeit a general rule of thumb is to avoid specializing function templates as specializations do not participate in the first step of overload resolution, there are arguable some contexts where it can be useful. E.g. when using a non-overloaded primary function template with no definition to match all types which one would not like implicitly converted to an otherwise matching-by-conversion overload; i.e., to implicitly remove a number of implicit-conversion matches by only implementing exact type matches in the explicit specialization of the non-defined, non-overloaded primary function template.
Before the deleted function concept of C++11, one could do this by simply omitting the definition of the primary function template, but this gave obscure undefined reference errors that arguably gave no semantic intent whatsoever from the author of primary function template (intentionally omitted?). If we instead explicitly delete the primary function template, the error messages in case no suitable explicit specialization is found becomes much nicer, and also shows that the omission/deletion of the primary function template's definition was intentional.
#include <iostream>
#include <string>
template< typename T >
void use_only_explicit_specializations(T t);
template<>
void use_only_explicit_specializations<int>(int t) {
std::cout << "int: " << t;
}
int main()
{
const int num = 42;
const std::string str = "foo";
use_only_explicit_specializations(num); // int: 42
//use_only_explicit_specializations(str); // undefined reference to `void use_only_explicit_specializations< ...
}
However, instead of simply omitting a definition for the primary function template above, yielding an obscure undefined reference error when no explicit specialization matches, the primary template definition can be deleted:
#include <iostream>
#include <string>
template< typename T >
void use_only_explicit_specializations(T t) = delete;
template<>
void use_only_explicit_specializations<int>(int t) {
std::cout << "int: " << t;
}
int main()
{
const int num = 42;
const std::string str = "foo";
use_only_explicit_specializations(num); // int: 42
use_only_explicit_specializations(str);
/* error: call to deleted function 'use_only_explicit_specializations'
note: candidate function [with T = std::__1::basic_string<char>] has
been explicitly deleted
void use_only_explicit_specializations(T t) = delete; */
}
Yielding a more more readable error message, where the deletion intent is also clearly visible (where an undefined reference error could lead to the developer thinking this an unthoughtful mistake).
Returning to why would we ever want to use this technique? Again, explicit specializations could be useful to implicitly remove implicit conversions.
#include <cstdint>
#include <iostream>
void warning_at_best(int8_t num) {
std::cout << "I better use -Werror and -pedantic... " << +num << "\n";
}
template< typename T >
void only_for_signed(T t) = delete;
template<>
void only_for_signed<int8_t>(int8_t t) {
std::cout << "UB safe! 1 byte, " << +t << "\n";
}
template<>
void only_for_signed<int16_t>(int16_t t) {
std::cout << "UB safe! 2 bytes, " << +t << "\n";
}
int main()
{
const int8_t a = 42;
const uint8_t b = 255U;
const int16_t c = 255;
const float d = 200.F;
warning_at_best(a); // 42
warning_at_best(b); // implementation-defined behaviour, no diagnostic required
warning_at_best(c); // narrowing, -Wconstant-conversion warning
warning_at_best(d); // undefined behaviour!
only_for_signed(a);
only_for_signed(c);
//only_for_signed(b);
/* error: call to deleted function 'only_for_signed'
note: candidate function [with T = unsigned char]
has been explicitly deleted
void only_for_signed(T t) = delete; */
//only_for_signed(d);
/* error: call to deleted function 'only_for_signed'
note: candidate function [with T = float]
has been explicitly deleted
void only_for_signed(T t) = delete; */
}
= delete is a feature introduce in C++11. As per =delete it will not allowed to call that function.
In detail.
Suppose in a class.
Class ABC{
Int d;
Public:
ABC& operator= (const ABC& obj) =delete
{
}
};
While calling this function for obj assignment it will not allowed. Means assignment operator is going to restrict to copy from one object to another.
New C++0x standard. Please see section 8.4.3 in the N3242 working draft
This is new thing in C++ 0x standards where you can delete an inherited function.
A small example to summarize some common usages:
class MyClass
{
public:
// Delete copy constructor:
// delete the copy constructor so you cannot copy-construct an object
// of this class from a different object of this class
MyClass(const MyClass&) = delete;
// Delete assignment operator:
// delete the `=` operator (`operator=()` class method) to disable copying
// an object of this class
MyClass& operator=(const MyClass&) = delete;
// Delete constructor with certain types you'd like to
// disallow:
// (Arbitrary example) don't allow constructing from an `int` type. Expect
// `uint64_t` instead.
MyClass(uint64_t);
MyClass(int) = delete;
// "Pure virtual" function:
// `= 0` makes this is a "pure virtual" method which *must* be overridden
// by a child class
uint32_t getVal() = 0;
}
TODO:
I still need to make a more thorough example, and run this to show some usages and output, and their corresponding error messages.
See also
https://www.stroustrup.com/C++11FAQ.html#default - section "control of defaults: default and delete"