What is the state of the art about functions to check whether a value is set or not?
For example, the below iterator parses cells. Some cells contain a value, other cells are empty.
What is the most convenient way?
struct iterator
{ //usage:
bool isset() const // if (it.isset())
bool isSet() const // if (it.isSet())
bool empty() const // if (it.empty())
bool is_set() const // if (it.is_set())
bool is_valid() const // if (it.is_valid())
operator void*() const; // if (it)
explicit operator bool() const; // if ((bool)it) or if(it) //thanks @stijn
operator bool() const; // if (it) //why not implicit conversion?
bool operator!() const; // if (!!it)
//throwing exception as pointed out by @MatthieuM
Type get() { if (isSet()) return value_; else throw; }
//usage:
// try { // if (it.isSet()) {
// Type x = it.get(); // Type x = it.get();
// } // }
// catch (...) { // else {
// //empty // //empty
// } // }
//please feel free to propose something different
...
};
Reflections:
- my boss does not understand
isset()
=> renamed toisSet()
empty()
is more about container collection, not just one single cell :(operator void*
seems to be the logical way but deprecated in C++11 streamsexplicit operator
is not yet supported (my code have to be compliant with old compilers)
I am reading:
I'm impressed that explicit_cast<T>
from Imperfect C++: Practical Solutions [...] hasn't been mentioned. The concept is very simple - you set up a pseudo-keyword that actually is a templated class implementing the conversion that you want. I've been using this in my own C++ backports library without any important issue.
class MyClass {
...implementation
operator explicit_cast<bool> () const {
(return a bool somehow)
}
};
You use the pseudo-keyword just as you would expect it to work:
MyClass value;
...
if ( explicit_cast<bool>(myobject) ) {
do something
} else {
do something else
}
...
The best part of all, this solution can be mapped perfectly to native explicit operator
in C++11, resulting in essentially zero overhead and native syntax. As a result, it's also more generic than trying to figure out if to call "isset", "is_set", "is_Set", "isSet", etc...
void*
has problems, as it is a valid conversion sequence that was not intended in some cases. Many people use in C++03 sometimes called "safe bool idiom" where you have a local member function pointer type that contains private types so no one could ever have an instance of it outside of your class. You can however return that and at least check for true/false.
When you are using C++11, then explicit operator bool
is the way to go, since it was mostly invented for exactly these cases.
Most of the time, one should not use implicit conversion, i.e. using expressions like operator bool()
in your code.
When you want to be able to use instances of your class in an if
statement, you will often create an implicit conversion, but to a signature of a member function prototype which you will either point to a no-op private function or to NULL dependent on the state.
You will also often overload bool operator!() const
for your class. As this will use the same logic as the implicit conversion, you will often implement one in terms of the other.
Something like:
private:
struct MyPrivateType {};
void MyPrivateFunc( MyPrivateType ) {}
public:
typedef void (&iterator::*)( MyPrivateType ) bool_func;
operator bool_func() const
{
return operator!() ? static_cast<bool_func>(0) : MyPrivateFunc;
}
Nobody can ever call the function you return from the pointer because it requires a MyPrivateType, and they can't get one because it's private.
Thanks to all your comments/answers/contributions. Here I have merged the different ideas and other ideas found on the net (I have read lots of documentation and source code).
Check the value presence
1. bool isSet()
as pointed out by @j_random_hacker
The most logical way. Both source code (library and application) are beginner understandable. And this fits the KISS principle. Moreover this is portable to other programming languages as Java...
Library: | Application:
|
struct iterator |
{ |
bool isSet() const | if (it.isSet())
{ | {
return p; | int v = it.get();
} | //get() may also call isSet()
|
int get() const | //continue processing
{ | }
return *p; | else //value is not set
} | {
| //do something else
int* p; | }
}; |
If the function get()
does not check isSet()
and a developer (of an application) forgets to call isSet()
(before get()
) then the application code may crash (segmentation fault).
In the other hand, if get()
function calls isSet()
therefore isSet()
processing is performed twice. Nevertheless recent compilers should avoid such second unnecessary isSet()
processing.
2. Return a flag value or default value as proposed by one of my colleagues
Library: | Application:
|
struct iterator | int i = it.get()
{ | if (i >= 0)
int get() const | {
{ | unsigned short v = i;
if(p) return *p; |
else return -1; | //continue processing
} | }
| else //value is not set
unsigned short* p; | {
}; | //do something else
| }
3. Throwing exception as pointed out by @Matthieu M
Some people think exception is bad for binary code optimization. However, if the throw exception
is inlined, best compilers may optimize the binary code, and better than the
Moreover, this solution may allow the best optimized binary code because isSet()
is called twice. But this depends on compiler optimization capacities.
Library:
struct iterator
{
bool get() const
{
if (isSet()) return *p;
else throw;
}
private:
bool isSet() const { return ....; }
....
};
Application:
int value;
try
{
value = it.get();
}
catch (...)
{
value = 0; // default value
}
4. Using operator explicit_cast<bool> () const
Please refer to well written Luis's answer.
5. Using operator
to write elegant if(it)
This solution can be well implemented using explicit conversion operators introduced in C++11.
Library:
struct iterator
{
explicit operator bool() const { return ....; }
....
};
Application:
int value;
if (it) //very elegant C++ fashion
{
value = it.get();
}
else
{
value = 0; // default value
}
However, we are still in 2012, and current source code must be compliant with compilers without support of explicit conversion operators. On theses compilers, different possibilities have been implemented years after years. I present all these ones on the next chapter.
Enable statement if(it)
before C++11
The source code of this chapter is inspired from the book More C++ idioms written by Bjarne Stroustrup in 2004, more specifically the section The Safe Bool Idiom as pointed out by @PlasmaHH.
1. implicit operator bool
When explicit
is not available, we could just use the implicit conversion operator.
Library:
struct iterator
{
operator bool() const { return ....; } //implicit conversion
....
};
Application:
int value;
if (it) //this works very well!
{
value = it.get();
}
else
{
value = 0; // default value
}
// But these other instructions are also correct :(
int integer = it; //convert it to bool, then convert bool to int
if (-6.7 < it) //.................., then convert bool to double, and compare
it << 1;
2. operator!
This is the solution used in boost::thread
(v1.51) as a workaround of explicit operator bool()
for unique_lock
, shared_lock
, upgrade_lock
and upgrade_to_unique_lock
.
Library:
struct iterator
{
bool operator!() const { return ....; }
....
};
Application:
int value;
if (!!it) // !! looks strange for many developers
{
value = it.get();
}
else
{
value = 0; // default value
}
if (it) //ERROR: could not convert ‘it’ from ‘iterator’ to ‘bool’
{
value = it.get();
}
3. operator void*
This is the solution used by STL streams. For example refer to file bits/basic_ios.h (std::basic_ios
).
Library:
struct iterator
{
operator void*() const { return ....; }
....
};
Application:
int value;
if (it) //this works very well!
{
value = it.get();
}
else
{
value = 0; // default value
}
// But these other instructions are also correct :(
delete it; //just a warning: deleting 'void*' is undefined
if (it > std::cin) //both are converted to void*
void* r = it;
4. implicit conversion to an undefined nested class
This solution has been proposed by Don Box in 1996.
Library:
struct iterator
{
private:
class nested; //just a forward declaration (no definition)
int* v_;
public:
operator nested*() const { return v_ ? (nested*)this : 0; }
};
Application:
int value;
if (it) //this works very well!
{
value = it.get();
}
else
{
value = 0; // default value
}
// But these other instructions are also correct :(
iterator it2;
if (it < it2)
int i = (it == it2);
5. Safe bool
idiom as presented by @CashCow
Bjarne Stroustrup has proposed the utimate solution without drawbacks. Below is a simplified version.
Library:
struct iterator
{
private:
typedef bool (iterator::*bool_type)() const;
bool private_() const {}
int* v_;
public:
operator bool_type() const { return v_ ? &iterator::private_ : 0; }
};
//forbids it1 == it2
template <typename T>
bool operator == (const iterator& it,const T& t) { return it.private_(); }
//forbids it1 != it2
template <typename T>
bool operator != (const iterator& it,const T& t) { return ! (it == t); }
Application:
int value;
if (it) //this works very well!
{
value = it.get();
}
else
{
value = 0; // default value
}
// All other instructions fail to compile
iterator it2;
if (it > it2) ; //ERROR: no match for ‘operator>’ in ‘it > it2’
if (it == it2) ; //ERROR: ‘bool iterator::private_() const’ is private
if (it != it2) ; //same error
6. Reusable safe bool
idiom
This is much more complex, please refer to Wikibooks for source code.
Library:
struct iterator : safe_bool <iterator> //I do not want virtual functions
{
bool boolean_test() const { return ....; }
....
};
Recent STL and boost provide facilities. Some examples:
- GNU STL --> see files tr1/functional and exception_ptr.h
- Each Boost component uses its own
safe_bool
:- Spirit --> see files spirit/include/classic_safe_bool.hpp and spirit/home/classic/core/safe_bool.hpp
- IOstream --> see file iostreams/device/mapped_file.hpp
- Parameter --> parameter/aux_/maybe.hpp
- Optional
- Function
- Range
- Logic (tribool)
But Matthew Wilson says in his book Imperfect C++ that safe_bool
may lead to size penalties on compilers not implementing Empty Base Optimization. Although most modern compilers do when it comes to single inheritance, there may be a size penalty with multiple inheritance.
来源:https://stackoverflow.com/questions/13193766/isset-or-operator-void-or-explicit-opertor-bool-or-something-else