6

In the class below,

Why would you make the operators explicit. I thought that explicit was to prevent implicit calling of constructors?

 class Content
            {
public:

 virtual ~Content() = 0;
 virtual explicit operator float&();
 virtual explicit operator long long&();
 virtual explicit operator std::string&()
}
YSC
  • 38,212
  • 9
  • 96
  • 149
cherry aldi
  • 327
  • 1
  • 10

4 Answers4

4

I thought that explicit was to prevent implicit calling of constructors?

Since C++11 it also applies to user-defined conversions (a.k.a. the cast operator).

Why would you make the operators explicit

Used in this context, the explicit keyword makes the conversion eligible only for direct-initialization and explicit conversions. See here under [class.conv.fct¶2]:

A conversion function may be explicit ([dcl.fct.spec]), in which case it is only considered as a user-defined conversion for direct-initialization ([dcl.init]). Otherwise, user-defined conversions are not restricted to use in assignments and initializations.

This aids you in making sure the compiler doesn't try the conversion against your intention, so that you have to explicitly cast it yourself, leaving less room for error. Example:

struct Foo
{
    explicit operator int() {return 0;}
    operator int*() {return nullptr;}
};

int main()
{
    Foo foo;

    //int xi = foo; // Error, conversion must be explicit
    int i = static_cast<int>(foo); // OK, conversion is explicit
    int* i_ptr = foo; // OK, implicit conversion to `int*` is allowed

    int i_direct(foo); // OK, direct initialization allowed
    int* i_ptr_direct(foo); // OK, direct initialization after implicit conversion

    return 0;
}

It can also help resolve ambiguity in cases where multiple conversion options apply, leaving the compiler without a criteria for deciding which one to choose:

struct Bar
{
    operator int() {return 1;}
    operator char() {return '1';}
};

int main()
{
    Bar bar;    
    //double d = bar; // Error, implicit conversion is ambiguous    
    return 0;
}

Add explicit:

struct Bar
{
    operator int() {return 1;}
    explicit operator char() {return '1';}
};

int main()
{
    Bar bar;    
    double d = bar; // OK, implicit conversion to `int` is the only option    
    return 0;
}
Geezer
  • 5,600
  • 18
  • 31
  • IMHO, as a general rule conversions (if these are absolutely necessary) of user-defined types should be member function, for example: `int Foo::toInt()`, `bool Foo::toBool()`; then... if it is convenient for clarity/security in the client code, to define an explicit conversion operator (say, `explicit Foo::operator int()`), and only if there are very powerful reasons, an implicit conversion operator. It is much more important to be (clear, explicit, simple) than to be clever. – Álvaro Gustavo López Sep 25 '18 at 13:44
  • @ÁlvaroGustavoLópez Agreed : ) But speaking of being explicit (ha) I was trying to explicitly answer the question asked -- namely, regarding usage of the `explicit` keyword for conversions. – Geezer Sep 25 '18 at 13:50
  • Mine is a comment, not an answer. – Álvaro Gustavo López Sep 25 '18 at 14:14
  • @ÁlvaroGustavoLópez you're 100% OK, I was just being apologetic about why not include it in the answer in the first place : ) – Geezer Sep 25 '18 at 14:17
2

Consider the following:

struct Content
{
    operator float() { return 42.f; }  
    friend Content operator+(Content& lhs, float) { return lhs; }
};

int main()
{
    Content c{};
    c + 0; //  error: ambiguous overload for 'operator+'
}

Here, the compiler cannot choose between operator+(Content&, float) and operator+(float, int). Making the float operator explicit resolves this ambiguity*:

c + 0; // operator+(Content&, float)

or

static_cast<float>(c) + 0; // operator+(float, int)

*) provided it makes sense to prefer one over the other.

YSC
  • 38,212
  • 9
  • 96
  • 149
2

The other answers cover how it works, but I think you should be told why it was added to C++.

A smart pointer usually has a conversion to bool so you can do this:

std::shared_ptr<int> foo;
if (foo) {
  *foo = 7;
}

where if(foo) converts foo to bool. Unfortunately:

int x = foo+2;

converts foo to bool, then to int, then adds 2. This is almost always a bug. This is permitted because while only one user defined conversion is done, a user defined conversion followed by a built in conversion can silently occur.

To fix this programmers would do crazy things like add:

struct secret {
  void unused();
};
struct smart_ptr {
  using secret_mem_ptr = void(secret::*)();
  operator secret_mem_ptr() const { return 0; }
};

and secret_mem_ptr is a secret pointer to member. A pointer to member has a built in conversion to bool, so:

smart_ptr ptr;
if (!ptr) {
}

"works" -- ptr is convert to secret_mem_ptr, which then is convered to bool, which is then used to decide which branch to take.

This was more than a bit of a hack.

They added explicit on conversion operators to solve this exact problem.

Now:

struct smart_ptr {
  explicit operator bool() const { return true; }
};

doesn't permit:

smart_ptr ptr;
int x = 3 + ptr;

but it does permit:

if (ptr) {
}

because the rules were hand-crafted to support exactly that use case. It also doesn't permit:

bool test() {
  smart_ptr ptr;
  return ptr;
}

here, you have to type:

bool test() {
  smart_ptr ptr;
  return (bool)ptr;
}

where you explicitly convert ptr to bool.

(I am usually really against C-style casts; I make an exception in the case of (bool)).

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • "*I am usually really against C-style casts; I make an exception in the case of `(bool)`*" - why? You should always use `static_cast` when invoking explicit conversions: `return static_cast(ptr);` If such a conversion is defined, it will be invoked, otherwise the code will fail to compile – Remy Lebeau Sep 24 '18 at 18:20
  • @RemyLebeau Find me a case where `(bool)(x)` does the wrong (or dangerous) thing and `static_cast(x)` does the right thing (including fail to compile due to an error when the cast would be dangerous). If you can, I will reconsider, but I could not. – Yakk - Adam Nevraumont Sep 24 '18 at 18:39
1

You would use it if you wanted a Content object never to be implicitly converted to (say) a float. This could happen in the following way:

 void f( float f );
 ....
 Content c;
 f( c );      // conversion from Content to float

Without the explicit qualifier, the conversion happens implicitly; with it, you get a compilation error.

Silent, implicit conversions can be the source of a lot of confusion and/or bugs, so it's generally better to make the operators explicit , or probably better yet to provide named functions, such as ToFloat, which tell the reader exactly what is going on.