Error use of deleted function

Use of deleted function c++: In the previous article, we have discussed about Unary Operator Overloading in CPP using both member & Friend function. Let us learn About ‘delete’ keyword and deleted functions with Use Cases in C++ Program. Understanding the use of ‘delete’ keyword C++ delete function: In this article we will discuss about a ... Read more

Use of deleted function c++: In the previous article, we have discussed about Unary Operator Overloading in CPP using both member & Friend function. Let us learn About ‘delete’ keyword and deleted functions with Use Cases in C++ Program.

Understanding the use of ‘delete’ keyword

C++ delete function: In this article we will discuss about a keyword delete, through which we can make function incapable and ineffective.

Deleting Copy Constructor and Assignment Operator :

Error: use of deleted function: We can’t call a deleted function if we do so, then it will generate compile time error.

#include <iostream>

#include <string>

class Student

{

int sroll;

std::string sname;

public:

Student(int studRoll, std::string studName) : sroll(studRoll), sname(studName)

{}

// to delete the Copy Constructor

Student(const Student & obj) = delete;

// to delete the Assignment operator

Student & operator = (const Student & obj) = delete;

void display()

{

std::cout<<sroll << " ::: "<<sname<<std::endl;

}

};

int main()

{

Student userObj(10, "Rick");

Student obj = userObj;




return 0;

}
Output :
main.cpp: In function ‘int main()’:
main.cpp:31:19: error: use of deleted function ‘Student::Student(const Student&)’
Student obj = userObj;
^~~~~~~
main.cpp:19:5: note: declared here
Student(const Student & obj) = delete;
^~~~~~~

Here class Student, Copy Constructor, Assignment Operator are deleted, so if we call deleted function we will get compilation error.

Deleting member functions to prevent data loss conversions :

We if the Student constructor accepts studRoll as int, we can still call it by double & char too but it is a invalid conversions. So we can prevent it, by using delete keyword and declaring them as deleted.

It will throw a compilation error as Student object with double and char will be deleted.

#include <iostream>

#include <string>

class Student

{

int sroll;

std::string sname;

public:

Student(int studRoll, std::string studName) : sroll(studRoll), sname(studName)

{}

// To delete a constructor that accepts a double as roll

Student(double studRoll, std::string studName) = delete ;

// To delete a constructor that accepts a double as roll

Student(char studRoll, std::string studName) = delete ;




// to delete Copy Constructor

Student(const Student & obj) = delete;

// to delete Assignment operator

Student & operator = (const Student & obj) = delete;

void display()

{

std::cout<<sroll << " ::: "<<sname<<std::endl;

}

};

int main()

{

Student obj1(5.57, "Rick");

Student obj2('z', "Satya");

return 0;

}
Output :
main.cpp:35:30: error: use of deleted function ‘Student::Student(double, std::string)’
Student obj1(5.57, "Rick");
^
main.cpp:19:1: note: declared here
Student(double studRoll, std::string studName) = delete ;
^~~~~~~
main.cpp:37:30: error: use of deleted function ‘Student::Student(char, std::string)’
Student obj2('z', "Satya");
^
main.cpp:21:1: note: declared here
Student(char studRoll, std::string studName) = delete ;
^~~~~~~

Restrict Object creation on Heap by deleting new operator for class :

By deleting new operator of class Student, when we will try to create Student Object on heap using new we will get compilation.

#include <iostream>

#include <string>

class Student

{

int sroll;

std::string sname;

public:

Student(int studRoll, std::string studName) : sroll(studRoll), sname(studName)

{}

// to prevent object creation on heap deleting the new function

void * operator new (size_t) = delete;

};

int main(){

Student * ptr = new Student(21, "Rick");

}
Output :
main.cpp: In function ‘int main()’:
main.cpp:23:43: error: use of deleted function ‘static void* Student::operator new(size_t)’
Student * ptr = new Student(1, "Riti");
^
main.cpp:19:12: note: declared here
void * operator new (size_t) = delete;
^~~~~~~~

Delete specific template specialization :

We can also restrict some certain specializations of template classes or functions using delete keyword.

#include <iostream>

#include <string>

template <typename T>

class ComplexNumber

{

T x;

T y;

public:

ComplexNumber(T a, T b) : x(a) , y(b)

{}

void display()

{

std::cout<<x << " + i"<<y<<std::endl;

}

// to deleted template specialisation

ComplexNumber(char a, char b) = delete;

// to deleted template specialisation

ComplexNumber(double a, double b) = delete;

};

int main(){

ComplexNumber<int> obj1(1,2);

ComplexNumber<double> obj2(1.0,2.0);

ComplexNumber<char> obj3('1' , '2');

return 0;

}
Output :
main.cpp:27:5: error: ‘ComplexNumber::ComplexNumber(double, double) [with T = double]’ cannot be overloaded
ComplexNumber(double a, double b) = delete;
^~~~~~~~~~~~~
main.cpp:18:5: error: with ‘ComplexNumber::ComplexNumber(T, T) [with T = double]’
ComplexNumber(T a, T b) : x(a) , y(b)
^~~~~~~~~~~~~
main.cpp: In instantiation of ‘class ComplexNumber<char>’:
<span class="error_line" onclick="ide.gotoLine('main.cpp',32)">main.cpp:32:25</span>:   required from here
main.cpp:25:5: error: ‘ComplexNumber::ComplexNumber(char, char) [with T = char]’ cannot be overloaded
ComplexNumber(char a, char b) = delete;
^~~~~~~~~~~~~
main.cpp:18:5: error: with ‘ComplexNumber::ComplexNumber(T, T) [with T = char]’
ComplexNumber(T a, T b) : x(a) , y(b)
^~~~~~~~~~~~~

Difference between deleted function and private functions :

By declaring function as private we restrict its calling, but making function deleted as many benefits like:

  • By deleting function with deleted keyword, the deleted functions can’t be called by other member functions, but private member functions can be even called from other member functions.
  • If compiler finds a function is deleted then it will not lookup for other matching functions as Deleted function exist in lookups which will prevent unnecessary bugs and data loss.

In this article we will discuss how to use delete keyword with functions and its use cases.

Deleting Functions using ‘delete’ keyword

In C++11 a new feature of keyword delete is introduced. Now we can apply the delete keyword to functions to make them uncallable.

void someFunction() = delete ;

Its practical use cases are,

  • Deleting compiler generated functions like copy constructor, assignment operators, move constructor , move assignment operator and default constructor.
  • Deleting member functions to prevent data loss conversions
  • Restrict Object creation on Heap by deleting new operator for class
  • Delete specific template specilaizations

Let’s discuss them one by one i.e.

Deleting Copy Constructor and Assignment Operator

class User
{

	int id;
	std::string name;
public:
	User(int userId, std::string userName) : id(userId), name(userName)
	{}

	// Copy Constructor is deleted
	User(const User & obj) = delete;
	// Assignment operator is deleted
	User & operator = (const User & obj) = delete;

	void display()
	{
		std::cout<<id << " ::: "<<name<<std::endl;
	}

};

In the above class User, Copy Constructor and Assignment Operator are deleted. If anybody tries to call the deleted function then it will generate compile time error.
For example,

Advertisements

User userObj(3, "John");

User obj = userObj;

As copy constructor is deleted, therefore above lines will throw following compile time error i.e.

delete_example.cpp: In function ‘int main()’:
delete_example.cpp:136:13: error: use of deleted function ‘User::User(const User&)’
  User obj = userObj;
             ^
delete_example.cpp:111:2: note: declared here
  User(const User & obj) = delete;

A part from applying delete keywords to compiler generated functions, we can apply delete keyword to other functions too.

Deleting member functions to prevent data loss conversions

Some times due to automatic type conversion we can call the functions with wrong parameters too. For example, for User class we have this parameterized constructor i.e.

User(int userId, std::string userName) : id(userId), name(userName)
{}

Even if this constructor accepts id as int, still we can call this by double 7 char too i.e.

User obj4(5.5, "Riti");

User obj5('a', "Riti");

It will work but these are invalid conversions, with delete keyword we can prevent them too by declaring them deleted i.e.

// Deleting a constructor that accepts a double as ID to prevent narrowing conversion
User(double userId, std::string userName) = delete ;

// Deleting a constructor that accepts a double as ID to prevent invalid type conversion
User(char userId, std::string userName) = delete ;

Now on creating User object with double and char will throw compile time error i.e.

error: use of deleted function ‘User::User(double, std::__cxx11::string)’
  User obj4(5.5, "Riti");
                      ^
note: declared here
  User(double userId, std::string userName) = delete ;

error: use of deleted function ‘User::User(char, std::__cxx11::string)’
  User obj5('a', "Riti");
                       ^
note: declared here
  User(char userId, std::string userName) = delete ;

Restrict Object creation on Heap by deleting new operator for class

We can delete the new operator of class User i.e.

class User
{
	int id;
	std::string name;
public:
	User(int userId, std::string userName) : id(userId), name(userName)
	{}

	// Delete the new function to prevent object creation on heap
	void * operator new (size_t) = delete;

};

Now if we try to create User Object on heap using new, it will give compile time error i.e.

User * ptr = new User(1, "Riti");

Compile Error:

error: use of deleted function ‘static void* User::operator new(size_t)’
  User * ptr = new User(1, "Riti");
                                 ^
note: declared here
  void * operator new (size_t) = delete;

Complete example is as follows,

#include <iostream>
#include <string>

class User
{

	int id;
	std::string name;
public:
	User(int userId, std::string userName) : id(userId), name(userName)
	{}

	// Copy Constructor is deleted
	User(const User & obj) = delete;
	// Assignment operator is deleted
	User & operator = (const User & obj) = delete;

	void display()
	{
		std::cout<<id << " ::: "<<name<<std::endl;
	}

	// Deleting a constructor that accepts a double as ID to prevent narrowing conversion
	User(double userId, std::string userName) = delete ;

	// Deleting a constructor that accepts a double as ID to prevent invalid type conversion
	User(char userId, std::string userName) = delete ;

	// Delete the new function to prevent object creation on heap
	void * operator new (size_t) = delete;

};


int main()
{
	User userObj(3, "John");

	// Can not copy User object as copy constructor is deleted
	//User obj = userObj;


	/*
	 * Creating User objects with double or char as ID will cause compile time error
	 *
	User obj4(5.5, "Riti");

	obj4.display();

	User obj5('a', "Riti");

	obj5.display();
	 */

	// Can not create object on heap as new operater is deleted

	//User * ptr = new User(1, "Riti");

	return 0;
}

Delete specific template specialisation

With delete keyword we can restrict certain template specialisations of template classes or functions. Let’s see how to do that,
Suppose we have a template class of Complex Number

template <typename T>
class ComplexNumber
{
	T x;
	T y;
public:
	ComplexNumber(T a, T b) : x(a) , y(b)
	{}
	void display()
	{
		std::cout<<x << " + i"<<y<<std::endl;
	}
};

We can use ComplexNumber with different types like int, double & char etc. i.e.

ComplexNumber<int> obj1(1,2);

ComplexNumber<double> obj2(1.0,2.0);

ComplexNumber<char> obj3('1' , '2');

Now we want to restrict that no one can use CompleNumber class with double or char as template parameter. For that we need to delete certain specialized functions i.e.

ComplexNumber(char a, char b) = delete;

ComplexNumber(double a, double b) = delete;

Now on trying to use ComplexNumber<> class with double or char as template parameter will generate compile time error.

Complete ComplexNumber class with deleted specialisations is as follows,

template <typename T>
class ComplexNumber
{
	T x;
	T y;
public:
	ComplexNumber(T a, T b) : x(a) , y(b)
	{}
	void display()
	{
		std::cout<<x << " + i"<<y<<std::endl;
	}
        // Deleted template specialisation 
	ComplexNumber(char a, char b) = delete;
        // Deleted template specialisation  
	ComplexNumber(double a, double b) = delete;
};

Different between deleted function and private functions

By making functions private we can also restrict its calling but still marking function deleted with delete keyword has its own advantages like,

  • Private member functions can be called from other member functions, whereas, deleted functions can not be called even from other member functions.
  • Deleted functions exists in name lookup , if compiler finds a function is deleted then it will not lookup for other matching functions based on type lookups, hence prevents unneccesary data loss and bugs.

Advertisements

Thanks for reading.

Recently a coworker came across a C++ compiler error message that seemed baffling, as they sometimes tend to be.

We figured it out together, and in the hope of perhaps saving some others form being stuck on it too long, I thought I’d describe it.

The code pattern that triggers the error can be distilled down into the following:

#include <utility>  // for std::move

// A type that's move-only.
struct MoveOnly {
  MoveOnly() = default;
  
  // copy constructor deleted
  MoveOnly(const MoveOnly&) = delete;  
  
  // move constructor defaulted or defined
  MoveOnly(MoveOnly&&) = default;      
};

// A class with a MoveOnly field.
struct S {
  MoveOnly field;
};

// A function that tries to move objects of type S
// in a few contexts.
void foo() {
  S obj;
  // move it into a lambda
  [obj = std::move(obj)]() {    
    // move it again inside the lambda
    S moved = std::move(obj);   
  }();
}

The error is:

test.cpp: In lambda function:
test.cpp:19:28: error: use of deleted function ‘S::S(const S&)’
   19 |     S moved = std::move(obj);
      |                            ^
test.cpp:11:8: note: ‘S::S(const S&)’ is implicitly deleted because the default definition would be ill-formed:
   11 | struct S {
      |        ^
test.cpp:11:8: error: use of deleted function ‘MoveOnly::MoveOnly(const MoveOnly&)’
test.cpp:6:3: note: declared here
    6 |   MoveOnly(const MoveOnly&) = delete;
      |   ^~~~~~~~

The reason the error is baffling is that we’re trying to move an object, but getting an error about a copy constructor being deleted. The natural reaction is: “Silly compiler. Of course the copy constructor is deleted; that’s by design. Why aren’t you using the move constructor?”

The first thing to remember here is that deleting a function using = delete does not affect overload resolution. Deleted functions are candidates in overload resolution just like non-deleted functions, and if a deleted function is chosen by overload resolution, you get a hard error.

Any time you see an error of the form “use of deleted function F“, it means overload resolution has already determined that F is the best candidate.

In this case, the error suggests S’s copy constructor is a better candidate than S’s move constructor, for the initialization S moved = std::move(obj);. Why might that be?

To reason about the overload resolution process, we need to know the type of the argument, std::move(obj). In turn, to reason about that, we need to know the type of obj.

That’s easy, right? The type of obj is S. It’s right there: S obj;.

Not quite! There are actually two variables named obj here. S obj; declares one in the local scope of foo(), and the capture obj = std::move(obj) declares a second one, which becomes a field of the closure type the compiler generates for the lambda expression. Let’s rename this second variable to make things clearer and avoid the shadowing:

// A function that tries to move objects of type S in a few contexts.
void foo() {
  S obj;
  // move it into a lambda
  [capturedObj = std::move(obj)]() {    
    // move it again inside the lambda
    S moved = std::move(capturedObj);
  }();
}

We can see more clearly now, that in std::move(capturedObj) we are referring to the captured variable, not the original.

So what is the type of capturedObj? Surely, it’s the same as the type of obj, i.e. S?

The type of the closure type’s field is indeed S, but there’s an important subtlety here: by default, the closure type’s call operator is const. The lambda’s body becomes the body of the closure’s call operator, so inside it, since we’re in a const method, the type of capturedObj is const S!

At this point, people usually ask, “If the type of capturedObj is const S, why didn’t I get a different compiler error about trying to std::move() a const object?”

The answer to this is that std::move() is somewhat unfortunately named. It doesn’t actually move the object, it just casts it to a type that will match the move constructor.

Indeed, if we look at the standard library’s implementation of std::move(), it’s something like this:

template <typename T>
typename std::remove_reference<T>::type&& move(T&& t)
{ 
    return static_cast<typename std::remove_reference<T>::type&&>(t); 
}

As you can see, all it does is cast its argument to an rvalue reference type.

So what happens if we call std::move() on a const object? Let’s substitute T = const S into that return type to see what we get: const S&&. It works, we just get an rvalue reference to a const.

Thus, const S&& is the argument type that gets used as the input to choosing a constructor for S, and this is why the copy constructor is chosen (the move constructor is not a match at all, because binding a const S&& argument to an S&& parameter would violate const-correctness).

An interesting question to ask is, why is std::move() written in a way that it compiles when passed a const argument? The answer is that it’s meant to be usable in generic code, where you don’t know the type of the argument, and want it to behave on a best-effort basis: move if it can, otherwise copy. Perhaps there is room in the language for another utility function, std::must_move() or something like that, which only compiles if the argument is actually movable.

Finally, how do we solve our error? The root of our problem is that capturedObj is const because the lambda’s call operator is const. We can get around this by declaring the lambda as mutable:

void foo() {
  S obj;
  [capturedObj = std::move(obj)]() mutable {
    S moved = std::move(capturedObj);
  }();
}

which makes the lambda’s call operator not be const, and all is well.

gracicot.github.io

Some programming blog


Project maintained by gracicot
Hosted on GitHub Pages — Theme by mattgraham

C++ templates is often blamed of horrible errors. Diagnostics can be painfully large for users of heavily templated libraries. And indeed, there can be pretty horrible errors only by using the STL.

Library writers often are confronted with a choice: being sfinae friendly, or output a nicely crafted compiler error with static_assert.

While experimenting ways to output errors in the cleanest way possible, I have found a trick to both enable sfinae while providing custom error messages that may depend on multiple conditions.

Do you even error?

Yeah… do you?

No, I didn’t meant you, I meant the compilers. Do they. I felt like necessary to compare some solutions I have found to provide nicer messages from the compiler when having errors in templates.

First, we will start with a non-sfinae enabled, simple template function that yield a compilation error:

template<typename A, typename B>
auto add(A a, B b) {
    return a + b;
}

int main() {
    struct {} someVar;

    add(someVar, 7);
}

Now let’s listen to the screams of pain of multiple compilers at the sight of this ill-formed program.

GCC

Ah! The good old GCC. The great pillar of GNU, that lighthouse of freedom in this proprietary sea, that everlasting source of… ok, let’s cut the crap. Here’s the output:

add.cpp: In instantiation of ‘auto add(A, B) [with A = main()::<anonymous struct>; B = int]’:
add.cpp:9:19:   required from here
add.cpp:3:14: error: no match for ‘operator+’ (operand types are ‘main()::<anonymous struct>’ and ‘int’)
 return a + b
        ~~^~~

Clang

I like to use clang sometime when GCC’s template bracktrace are not enough. It’s nice to see some complex metaprogramming errors from another perspective. It’s doing quite similar to GCC in this particular case:

add.cpp:3:14: error: invalid operands to binary expression ('(anonymous struct at add.cpp:7:5)' and 'int')
    return a + b;
           ~ ^ ~
add.cpp:9:5: note: in instantiation of function template specialization 'add<(anonymous struct at add.cpp:7:5), int>' requested here
add(someVar, 7);
^
1 error generated.

MSVC

Oh, MSVC. That compiler. It always had some quirks and bugs, but it has gotten a lot better recently. Let’s see how it goes.

add.cpp(3): error C2676: binary '+': 'main::<unnamed-type-someVar>' does not define this operator or a conversion to a type acceptable to the predefined operator
add.cpp(9): note: see reference to function template instantiation 'auto add<main::<unnamed-type-someVar>,int>(A,B)' being compiled
        with
        [
            A=main::<unnamed-type-someVar>,
            B=int
        ]

We can spot easily the difference in style compared to the two other competitors. However, it’s not that bad, no big deal.

static_assert

Static assertations are a very handful feature of C++11. It allowed us to make custom compilation errors, and allow library writers to provide useful diagnostics to help the user fix the code. The most important thing about it is that the message in the compilation error is written by the library writer. Here’s the same example as above with a static_assert, with gcc:

#include <type_traits>

template<typename...>
using void_t = void;

template<typename, typename, typename = void>
struct can_add : std::false_type {};

template<typename A, typename B>
struct can_add<A, B,
    void_t<decltype(std::declval<A>() + std::declval<B>())>
> : std::true_type {};

template<typename A, typename B>
auto add(A a, B b) {
    static_assert(can_add<A, B>::value, "Cannot add! You must send types that can add together.");
    return a + b;
}

You must think that the error is completly clean with that beautiful error message?

Not quite.

main.cpp: In instantiation of 'auto add(A, B) [with A = main()::<anonymous struct>, B = int]':
main.cpp:21:19:   required from here
main.cpp:14:5: error: static assertion failed: Cannot add! You must send types that can add together.
     static_assert(can_add<A, B>::value, "Cannot add! You must send types that can add together.");
     ^~~~~~~~~~~~~
main.cpp:15:14: error: no match for 'operator+' (operand types are 'main()::<anonymous struct>' and 'int')
     return a + b;
            ~~^~~

It’s indeed not that great, we still see the same error as before, but with a nice message added. One could think a static_assert will stop the compilation, just as throwing will halt the function. But compilers usually don’t stop at the first compilation error. Many, many other error can still appear after the static assert.

SFINAE

This feature of C++ of probably one of the best one that came to existence for meta-programming. It’s the building block of any library that want to introspect and validate types. As you noticed, I used it in the example above.

sfinae can also be used to prevent nasty compilation errors. Let’s change out example again!

template<typename A, typename B>
auto add(A a, B b) -> decltype(a + b) {
    return a + b;
}

This code is even simpler than the one with static_assert, and can prevent enormous, incomprehensible error output.

But there’s a catch. Look a this error message:

main.cpp: In function 'int main()':
main.cpp:9:19: error: no matching function for call to 'add(main()::<anonymous struct>, int)'
     add(someVar, 7);
                   ^
main.cpp:2:6: note: candidate: template decltype ((a + b)) add(A, B)
 auto add(A a, B b) -> decltype(a + b) {
      ^~~
main.cpp:2:6: note:   template argument deduction/substitution failed:
main.cpp: In substitution of 'template decltype ((a + b)) add(A, B) [with A = main()::<anonymous struct>, B = int]':
main.cpp:9:19:   required from here
main.cpp:2:34: error: no match for 'operator+' (operand types are 'main()::<anonymous struct>' and 'int')
 auto add(A a, B b) -> decltype(a + b) {
                             ~~^~~

Whoa! The error is a bit longer than expected! It shows us a lot of information information, but that information won’t be there if we use enable_if with a type trait.

Fortunately, one can do inverse-matching to a deleted function instead. This will allow us to control the quantity of compiler output. It is as easy to add as:

If the substitution failed for our add function, it will use this one, which is deleted. This yield to a really clean error:

main.cpp: In function 'int main()':
main.cpp:11:19: error: use of deleted function 'void add(...)'
     add(someVar, 7);
                   ^
main.cpp:6:6: note: declared here
 void add(...) = delete;
      ^~~

However, we loose a valuable information: the reason the error happened. We can easily add our error back with a static_assert inside the fallback function:

template<typename T = void>
void add(...) {
    static_assert(!std::is_same<T, T>::value,
        "Cannot add! You must send types that can add together."
    );
}

We got back our message! but there’s another catch: we broke sfinae. Users cannot test if one can use add with given parameter. It will match to our poisoned function, and it’s a valid function according to it’s signature.

Where are we going with this?

This is some examples of what programmers actually do to solve this problem. Most of the time, we will see code such as the very first example. At the very best, we may see something like the first sfinae example or with the fallback function asserting.

Best of both worlds

Well, I have found another solution that may allows you to add custom compilation error messages without breaking sfinae.

Remember the first example with static_assert, how the compiler tried to find further errors? Let’s exploit this to allow static_assert within a deleted function!

Let’s make a simple class that will throw the error:

struct NoAddError {
    template<typename T>
    NoAddError(T&&) {
        static_assert(!std::is_same<T, T>::value,
            "Cannot add! You must send types that can add together."
        );
    }
};

Simply instantiating a constructor will lead to the error message.

Now, in the deleted function we must find a way to “force” the compiler to go further and instantiate the constructor of this class.

However, we must not alter too much the signature of the function, because causing the static_assert by trying to instantiate the signature of the function will lead to a hard error.

The instanciation must only occur when you actually directly invoke the deleted function, not while instanciating the function signature.

The solution lies in the constructor of the error class. When invoking directly the deleted function, the compiler will try to find further errors and will instanciate the body of the constructor, effectively triggering the static assert.

Let’s change our deleted function to this:

void add(NoAddError, ...) = delete;
//       ^--- This class has a template constructor.
//            The compiler must check it's signature
//            to know if this function is a match.

What do we get in our compiler output?

main.cpp: In function 'int main()':
main.cpp:44:15: error: use of deleted function 'void add(NoAddError, ...)'
     add("", "");
               ^
main.cpp:28:6: note: declared here
 void add(NoAddError, ...) = delete;
      ^~~
main.cpp: In instantiation of 'NoAddError::NoAddError(T&&) [with T = const char (&)[1]]':
main.cpp:44:15:   required from here
main.cpp:16:9: error: static assertion failed: Cannot add! You must send types that can add together.
         static_assert(!std::is_same<T, T>::value,
         ^~~~~~~~~~~~~

Now we’re talking! We have a deleted function, that when called directly also fire a static_assert!

Here’s a another snippet that shows that sfinae is not gone:

template<typename A, typename B>
auto tryAdd(A a, B b) -> void_t<decltype(add(a, b))> {
    std::cout << add(a, b) << std::endl;
}

template<typename A, typename B>
auto tryAdd(A a, B b) -> std::enable_if_t<!can_add<A, B>::value> {
    std::cout << "Well, some runtime error." << std::endl;
}

int main() {
    tryAdd(1, 2); // calls first
    tryAdd("some", "test"); // calls second
}

See how it runs live at coliru

If there were no sfinae involved, the second call would be ambiguous.

Edit the file and try to call add("", "") directly, and you’ll get the static assert.

Default values

Another trick to trigger our static assert would be to use default values. Replace the constructor of NoAddError to take no parameter and add the parameter to the deleted function:

template<typename A, typename B, std::enable_if_t<!can_add<A, B>::value>* = nullptr>
void add(A, B, NoAddError = {}) = delete;
//                        ^---- Notice the default argument that calls the default constructor!

The same process goes: when invoked directly, the compiler will instanciate the constructor body, triggerring the static assert.

Multiple errors

It can happen a code misues function at many places, many times. Fortunatly, the error class trick handle those cases really well. Here’s the GCC output for wrongly call the add function four times:

main.cpp: In function 'int main()':
main.cpp:44:15: error: use of deleted function 'void add(NoAddError, ...)'
     add("", "");
               ^
main.cpp:28:6: note: declared here
 void add(NoAddError, ...) = delete;
      ^~~
main.cpp:45:15: error: use of deleted function 'void add(NoAddError, ...)'
     add("", "");
               ^
main.cpp:28:6: note: declared here
 void add(NoAddError, ...) = delete;
      ^~~
main.cpp:46:15: error: use of deleted function 'void add(NoAddError, ...)'
     add("", "");
               ^
main.cpp:28:6: note: declared here
 void add(NoAddError, ...) = delete;
      ^~~
main.cpp:47:15: error: use of deleted function 'void add(NoAddError, ...)'
     add("", "");
               ^
main.cpp:28:6: note: declared here
 void add(NoAddError, ...) = delete;
      ^~~
main.cpp: In instantiation of 'NoAddError::NoAddError(T&&) [with T = const char (&)[1]]':
main.cpp:44:15:   required from here
main.cpp:16:9: error: static assertion failed: Cannot add! You must send types that can add together.
         static_assert(!std::is_same<T, T>::value,
         ^~~~~~~~~~~~~

This is it! There is only one static assert, nicely placed at the end of the error list. The first error you see in you console is the most relevant. Neat!

Variadics

Variadic functions are quite different to deal with. We must introduce our error parameter somewhere without breaking argument deduction.

Here we cannot rely on default values to make the trick.

Let’s use another example here. We want to implement a callMe function that will call a function with some parameters:

template<typename F, typename... Args>
auto callMe(F function, Args&&... args) -> decltype(function(std::declval<Args>()...)) {
    return function(std::forward<Args>(args)...);
}

To support errors like before, we will need to change the error class, and change the first parameter of the deleted function to be that error class. The constructor of the error class will match the F template parameter, and will use Args... without breaking type deduction.

In order to check if the deleted function is a match, the compiler will need to check if the first argument can be sent to our NotCallableError constructor. This makes the compiler instanciating the constructor signature. Since the static_assert is inside the constructor body, the error must occur only if you try to effectively call that constructor. It’s signature is completely valid, and will trigger sfinae if called with wrong parameters. Let’s see how to implement that:

template<typename...>
using void_t = void;

template<typename, typename = void>
struct is_callable : std::false_type {};

template<typename Sig>
struct is_callable<Sig, void_t<std::result_of_t<Sig>>> : std::true_type {};

template<typename... Args>
struct NotCallableError {
    //                   v--- Constructor exist only if not callable.
    template<typename F, std::enable_if_t<!is_callable<F(Args...)>::value>* = nullptr>
    NotCallableError(F) {
        static_assert(!std::is_same<F, F>::value,
            "The function cannot be called given parameters."
        );
    }
};

Now, just add the deleted function and some utility to control when deduction happen:

template<typename T>
struct identity_t {
    using type = T;
};

template<typename T>
using identity = typename identity_t<T>::type;

template<typename... Args>
void callMe(NotCallableError<identity_t<Args>...>, Args&&...) = delete;

Now, calling the function with the wrong arguments will yield this error:

main.cpp: In function 'int main()':
main.cpp:45:34: error: use of deleted function 'void callMe(NotCallableError<identity_t<Args>...>, Args&& ...) [with Args = {const char (&)[5], main()::<lambda(int, bool)>&}]'
     callMe(lambda, "test", lambda)
                                  ^
main.cpp:39:6: note: declared here
 void callMe(NotCallableError<identity_t<Args>...>, Args&&...) = delete;
      ^~~~~~
main.cpp: In instantiation of 'NotCallableError<Args>::NotCallableError(F) [with F = main()::<lambda(int, bool)>; std::enable_if_t<(! is_callable<F(Args ...)>::value)>* <anonymous> = 0; Args = {const char (&)[5], main()::<lambda(int, bool)>&}]':
main.cpp:45:34:   required from here
main.cpp:27:9: error: static assertion failed: The function cannot be called with given parameters.
         static_assert(!std::is_same<F, F>::value,
         ^~~~~~~~~~~~~

Here’s the code snippet that resulted in this error.

It’s not as clean as I would like to be, but it’s still output our error properly, while not breaking sfinae.

The great thing about having the static assert in a separate class is that you can add new errors simply by adding a new constructor:

// let's assume the trait `is_function` exists.

template<typename... Args>
struct NotCallableError {
    template<typename F, std::enable_if_t<
        is_function<F>::value &&
        !is_callable<F(Args...)>::value>* = nullptr>
    NotCallableError(F) {
        static_assert(!std::is_same<F, F>::value,
            "The function cannot be called with given parameters."
        );
    }
    
    template<typename F, std::enable_if_t<!is_function<F>::value>* = nullptr>
    NotCallableError(F) {
        static_assert(!std::is_same<F, F>::value,
            "The first parameter must be a function."
        );
    }
};

I have used this pattern extensively in the library Kangaru, where a lot of error cases have been written in the same error class.

The catch

Well, the catch is, it only work great with GCC. Painful truth, but it’s not so bad. Users of your code using GCC will have full messages with a static assert, and others will get only a plain deleted function call error.

Even with that, other compilers will still output the struct name NoAddError since it’s in the signature of the function, which can still be useful if you don’t use GCC.

I have seen this working in some cases with clang, but is not as reliable as GCC for executing the trick.

If you already marked invalid overloads as deleted, using this trick won’t break source.

Dealing with other compilers

Writing good error messages should not only benefit users of GCC. The thing is, since all static assert are in a separated class, you can reuse them. Here’s an example of that:

template<typename F, typename... Args,
    std::enable_if_t<std::is_constructible<NotCallableError<Args...>, F>::value>* = nullptr>
void debug_callMe(F&& function, Args&&... args) {
    NotCallableError error{std::forward<F>(function)};
    (void)error;
}

template<typename F, typename... Args,
    std::enable_if_t<!std::is_constructible<NotCallableError<Args...>, F>::value>* = nullptr>
void debug_callMe(F function, Args&&... args) {
    static_assert(!std::is_same<F, F>::value, "No error detected.")
}

Using the function debug_callMe as if it was callMe will trigger directly the static assert inside NotCallableError constructor. If no errors are detected, this code will output No error detected. as a static assert.

Simple cases

Here’s another bonus trick that can be used for simple cases. If you only have one possible error, just like our add function, you can always put a string literal in decltype. The compiler will also output that string when you invoke the function:

template<typename A, typename B, std::enable_if_t<!can_add<A, B>::value>* = nullptr>
auto add(A, B) -> decltype("Cannot add! You must send types that can add together."
) = delete;

This will yield this compiler output:

main.cpp: In function 'int main()':
main.cpp:47:15: error: use of deleted function 'const char (& add(A, B))[55] [with A = const char*; B = const char*; std::enable_if_t<(! can_add<A, B>::value)>* <anonymous> = 0]'
     add("", "");
               ^
main.cpp:29:6: note: declared here
 auto add(A, B) -> decltype("Cannot add! You must send types that can add together."
      ^~~

The output is quite clear, cleaner than error classes, and less verbose. In fact, it’s the less verbose solution of all. Unfortunately, it’s not as extensible as the error class trick. When you have a lot of error cases, grouping them all in one place is really useful and easier to maintain.

Also, all errors will output the message in the string literal, and they will be scattered around in the compiler output. With the error class trick, only one static assert is fired and is placed at the end of the compiler output.

For clang, this same simple trick will result in the message being in the middle of the error:

main.cpp:47:5: error: call to deleted function 'add'
    add("", "");
    ^~~
main.cpp:29:6: note: candidate function [with A = const char *, B = const char *, $2 = nullptr] has been explicitly deleted
auto add(A, B) -> decltype("Cannot add! You must send types that can add together."
     ^
main.cpp:24:6: note: candidate template ignored: substitution failure [with A = const char *, B = const char *]: invalid operands to binary expression ('const char *' and 'const char *')
auto add(A a, B b) -> decltype(a + b) {
     ^                           ~
1 error generated.

At least this time, the message is there, as the error classes don’t quite work under clang.

Personally, I prefer the error class trick even for small cases. The error being a static_assert makes it clear for the various IDEs out there that parses the compiler output. That effectively makes it an error, as this simple case the message in inside a note.


That’s it for today! I hope you enjoyed it!

If you find something wrong or have suggestions, compliments, complains, or even insults, just leave a comment on the reddit post or send me a github issue, I’ll greatly appreciate any feedback!

You are using a function, which is marked as deleted.
Eg:

int doSomething( int ) = delete;

The =delete is a new feature of C++0x. It means the compiler should immediately stop compiling and complain «this function is deleted» once the user use such function.

If you see this error, you should check the function declaration for =delete.

To know more about this new feature introduced in C++0x, check this out.

I encountered this error when inheriting from an abstract class and not implementing all of the pure virtual methods in my subclass.

The error message clearly says that the default constructor has been deleted implicitly. It even says why: the class contains a non-static, const variable, which would not be initialized by the default ctor.

class X {
    const int x;
};

Since X::x is const, it must be initialized — but a default ctor wouldn’t normally initialize it (because it’s a POD type). Therefore, to get a default ctor, you need to define one yourself (and it must initialize x). You can get the same kind of situation with a member that’s a reference:

class X { 
    whatever &x;
};

It’s probably worth noting that both of these will also disable implicit creation of an assignment operator as well, for essentially the same reason. The implicit assignment operator normally does members-wise assignment, but with a const member or reference member, it can’t do that because the member can’t be assigned. To make assignment work, you need to write your own assignment operator.

This is why a const member should typically be static — when you do an assignment, you can’t assign the const member anyway. In a typical case all your instances are going to have the same value so they might as well share access to a single variable instead of having lots of copies of a variable that will all have the same value.

It is possible, of course, to create instances with different values though — you (for example) pass a value when you create the object, so two different objects can have two different values. If, however, you try to do something like swapping them, the const member will retain its original value instead of being swapped.

gcc 4.6 supports a new feature of deleted functions, where you can write

hdealt() = delete;

to disable the default constructor.

Here the compiler has obviously seen that a default constructor can not be generated, and =delete‘d it for you.

parsecer

3 / 3 / 2

Регистрация: 19.07.2015

Сообщений: 74

1

11.02.2016, 00:27. Показов 8726. Ответов 3

Метки нет (Все метки)


У меня есть два класса: Dog и Owner. В классе Dog есть public static член, имеющий тип Owner & (ссылка на экземпляр класса Owner). Но при попытке это все реализовать, возникает ошибка «use of deleted function ‘Dog& Dog::operator=(const Dog&)». Вот код классов и их конструкторов:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Dog 
    {
       private:
         Owner & m_owner;
         ...
       public:
           static Owner & m_breeder;
         ...
    }
 
class Owner
    {private:
         ...
     public:
         ...
      }

dog_methods.cpp

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 {
    #include "dog_class.h"
    #include <iostream>
    #include <cstdlib>
    #include <string>
    #include <ctime>
 
    Owner breeder("breeder");
    Owner & Dog::m_breeder=breeder;
    //methods here
    Dog () : m_owner(m_breeder) {}
 
Dog::Dog(std::string name, int size, int status, int loyalty, int sex, int price, std::vector<Dog> * market, Owner & owner) : m_owner(owner)
{
m_name=name;
m_size=size;
m_status=status;
m_loyalty=loyalty;
 
m_sex=sex;
m_price=price;
m_market=market; //pointer to the market
m_market->push_back(*this);
//break_out();
 }
 
 
 
  Dog::Dog() : m_owner(m_breeder)
{
m_name="Fang";
srand(time(NULL));
m_size=(rand()%10)+1; //small: random number from 1 to 10;
m_status=0; //not owned
m_loyalty=(rand()%10)+1; //from 1 to 10
Owner no_one("no_one");
//m_owner=no_one;
m_sex=rand()%2; //rand 0 or 1;
m_price=rand()%1001; //0 - 1000;
//show_data();
//break_out();
 }

owner_methods.cpp

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Owner::Owner()
{
 
m_name="random";
m_goodness=(rand()%10+1);
m_size=(rand()%10+1);
m_money=rand()%1001;
m_agility=(rand()%10+1);
m_int=(rand()%10+1);
//std::cout<<"Hi! I am the "<<m_name<<std::endl;
 }
  Owner::Owner(std::string name)
 {
 if (name=="no one")
 {
m_name="no one";
m_goodness=0;
m_size=0;
m_money=0;
m_agility=0;
m_int=0;
std::cout<<"Hi! I am the "<<m_name<<std::endl;
   }
   else
  {
m_name=name;
m_goodness=(rand()%10+1);
m_size=(rand()%10+1);
m_money=rand()%1001;
m_agility=(rand()%10+1);
m_int=(rand()%10+1);
std::cout<<"The '"<<m_name<<"' owner made"<<std::endl;
    }
  }
 
 
 Owner::Owner(std::string name, int goodness, int size, int money, int    agility, std::vector<Dog> doglist)
  {
m_name=name;
m_size=size;
m_money=money;
m_agility=agility;
for (int i=0;  i<doglist.size();i++)
    m_doglist.push_back(doglist[i]);
  }

__________________
Помощь в написании контрольных, курсовых и дипломных работ, диссертаций здесь



0



16495 / 8988 / 2205

Регистрация: 30.01.2014

Сообщений: 15,611

11.02.2016, 00:34

2

Лучший ответ Сообщение было отмечено parsecer как решение

Решение

parsecer, оператор присваивания в классе Dog опиши явно. Он deleted потому что ссылки не присваиваются (не переназначаются). Обыграй это как-то сам в операторе.



1



3 / 3 / 2

Регистрация: 19.07.2015

Сообщений: 74

11.02.2016, 00:42

 [ТС]

3

Спасибо за ответ, а что вызвало данную ошибку? В main присваивания Dog=Dog нигде не используется.



0



16495 / 8988 / 2205

Регистрация: 30.01.2014

Сообщений: 15,611

11.02.2016, 00:45

4

Цитата
Сообщение от parsecer
Посмотреть сообщение

Спасибо за ответ, а что вызвало данную ошибку?

Вектор требует от своих элементов быть либо copy-assignable, либо move-assignable.



1



Overload resolution is one of C++ most complicated things and yet it works most of the time without the need to think about it.
In this mini-series, I will show you how to control this complex machinery so it is even more powerful and completely under your control.

The first post shows you how to delete candidates and how you can use that to prevent implicit conversions.

C++11’s =delete

Most of you know that since C++11 you can specify = delete to inhibit the generation of the special member functions like copy or move constructors.
But less people know that you can use it on any function and delete it.

The standard simply specifies in the beginning of §8.4.3[dcl.fct.def.delete]:

1 A function definition of the form: attribute-specifier-seqopt decl-specifier-seqopt declarator virt-specifier-seqopt = delete ; is called a deleted definition. A function with a deleted definition is also called a deleted function.

2 A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.

This means you can write the following program:

void func() = delete;
	
int main()
{
	func();
}

And if you try to compile it, you get a similar error message:

prog.cpp: In function ‘int main()’:
prog.cpp:5:2: error: use of deleted function ‘void func()’
func();
^
prog.cpp:1:6: note: declared here
void func() = delete;

Now that feature is not very useful.
If you don’t want to have a function, simply don’t declare it at all!

Unless you are dealing with automatically generated functions of course.

But consider what happens if the function is overloaded:

#include <iostream>

void func(int)
{
	std::cout << "intn";
}

void func(double) = delete;

int main()
{
	func(5);
}

Now we have two versions of func, one taking an int and a deleted one taking a double.
On the first look it does not seem any more useful than before.
If you don’t want to have an overload, simply don’t declare it!

But take a second look and consider the consequences of §8.4.3:
A function with = delete at the end, isn’t only a declaration, it is also a definition!
And since name lookup only looks for matching declarations, a deleted function is a normal candidate that can participate in overload resolution.

But the compiler can see that the function has a deleted definition, so why is it considered?
Because it is completely legal to declare a function in a header file and delete it in the source file, since a deleted function provides a normal definition.
(It is not, my mistake).
And also because the standard says so.™

If you write func(5.0), you now call it with a double.
The compiler chooses the overload for double, because a deleted function participates in overload resolution, and complains that the function is deleted.

This prohibits passing double to func, even though it could be implictly converted.

Prohibiting implicit conversions

As shown above, you can delete candidates to avoid certain implicit conversions in overload resolution.

If you have one or more overloads of a function accepting a certain set of types,
you can also call it with types that are implictly convertible to the accepted types.
Often this is great and terse and avoids verbose boilerplate.

But sometimes these implicit conversion are not without loss or expensive.
User-defined conversions can be controlled by using explicit,
but the implicit conversions built-in in the language like double to int?
You can’t write explicit there.

Allowing lossy conversions was one of the many mistakes C made and C++ inherited.

But you can write another overload that takes the types you want to prohibit and delete it.

Let’s extend the example above by prohibiting all floating points, not only double:

void func(int)
{
	std::cout << "intn";
}

void func(float) = delete;
void func(double) = delete;
void func(long double) = delete;

Now you cannot call it with any floating point.

You should write an overload for each floating point type,
otherwise the call is ambigous.
E.g. when having only the deleted double overload,
calling it with a long double is ambigous between int and double,
since both conversions lead to a loss.
It works here but could have side-effects in more complicated examples.
So to be safe, just write it explicitly.

You could also use templates to generate the three overloads,
use SFINAE to enable it only for floating points:

template <typename T,
		  typename = std::enable_if_t<std::is_floating_point<T>::value>>
void func(T) = delete;

Yes, that’s ugly. I want concepts!

Probihiting implicit conversions: Temporaries

Some kind of implicit conversions can be especially bad:
Those user-defined conversions that create temporaries.

For example, passing a string literal to a function taking a std::string creates a temporary std::string to initialize the argument.
This can be especially surprising in the following case:

void func(const std::string &str);
...
func("Hello, this creates a temporary!");

Here the writer of func took a std::string by (const) reference because he or she doesn’t want to copy the string, because that can involve costly heap allocations.
But passing a string literal does involve heap allocations due to the temporary.
And since temporary (rvalues, that is) bind to const (lvalue) references, this works.

This is often behavior that is tolerated, but sometimes the cost can be too expensive to allow the (accidental) creation of the temporary.
In this case, a new overload can be introduced that takes a const char*, which is deleted:

void func(const std::string &str);
void func(const char*) = delete;
...
func("this won't compile");
func(std::string("you have to be explicit"));

On a related note, sometimes you have a function taking a const reference to something and the function stores a pointer to it somewhere.
Calling it with a temporary would not only be expensive, but fatal, since the temporary is — well — temporary and the pointer will soon point to a destroyed object:

void func(const T &obj)
{
	// store address somewhere outside the function
}
...
func(T()); // dangerous!

Here in this case we need the more general form of disallowing any temporary objects.
So we need an overload taking any rvalue, that is an overload taking an rvalue reference:

void func(const T &obj) {...}
void func(T &&) = delete;
...
func(T()); // does not compile

Note that T is a concrete type here, the deleted overload is no template and thus T&& is not a forwarding reference, but a normal rvalue reference.

This works, but it isn’t perfect.
Let’s say you have a function foo that returns a const T (for some reason):

const T foo();
void func(const T &obj) {...}
void func(T &&) = delete;
...
func(foo()); // does compile!

This compiles because a const rvalue does not bind to a non-const rvalue reference,
as such the lvalue overload is selected, which is — again — dangerous.

The solution? Simple, just use a const rvalue reference:

const T foo();
void func(const T &obj) {...}
void func(const T &&) = delete;
...
func(foo()); // does not compile

The deleted overload accepts any rvalue, const or non-const.
This is one of the few good use cases for const rvalue references.

This trick is also used in the standard library.
func is std::ref/std::cref, the temporary versions are deleted,
so that no std::reference_wrapper to a temporary is created.

Conclusion

Sometimes it can be useful to forbid certain kinds of implicit conversions in function overloading,
since they can be expensive or lead to loss.

This is especially true for temporaries that bind to const lvalue referenceres.
They can also be dangerous, if you take and store an address of the referenced object,
then you don’t want to allow temporaries as arguments.

To prevent such things, simply define new overloads that take the type which would be implictly converted
and mark it as deleted.
In the case of preventing temporaries, the new overload should take a const rvalue reference to the appropriate type.

Overload resolution will prefer an exact match and choose the deleted overload
which result in a compile-time error.

In the next post of this mini series, I will use this technique even further to improve error messages on failed overload resolution
and show you a way to completely customize the error message when a deleted function is chosen.

If you’ve liked this blog post, consider donating or otherwise supporting me.

This blog post was written for my old blog design and ported over. If there are any issues, please let me know.

Понравилась статья? Поделить с друзьями:
  • Error uploading file перевод
  • Error uploading file rockstar support
  • Error upload reason ffmpeg2opus fail
  • Error uplaycrashreporter cpp 262
  • Error uplay pc is not currently installed your game needs access