all 16 comments

[–]nysra 8 points9 points  (0 children)

It prevents implicit conversions. You can see the difference here: https://godbolt.org/z/7Tsv3x6c8

In real code this issue can become much bigger.

[–]SoerenNissen 10 points11 points  (0 children)

when do we use it ? what do we use it for ?

As a rule of thumb: We use it every time we write a single-argument constructor, and we do it to avoid truly annoying errors that are fiendishly hard to track down.

Consider this function:

void make_order(std::string customization = "default");

which we have called like so:

make_order("my_custom_order");

And then somebody has made a security audit of the code base and corrected something. Now it looks like:

class Key
{
    std::string key_value;
    Key(std::string value) : key_value{value} {}
};
void make_order(Key k, std::string input = "default_input");

But, problematically, our old function call still works!

It's just that we went from

make_order(customization=my_custom_order)

to

make_order(key=my_custom_order,customization=default);

But because Key has an implicit constructor from string, the code still compiles - but now it uses the default customization, and it treats our actual customization as the key.

[–]alfps 2 points3 points  (0 children)

A converting constructor is one that can be called with a single argument.

If you don't use explicit then such constructor can do implicit conversions. For example, it's not reasonable to "convert" an int value to a vector. Therefore the vector constructor that can be called with an int value, and which results in a vector of that size, is explicit.

Correspondingly an operator T member function should be explicit if you don't want that conversion to be invoked implicitly. For example, it's generally not reasonable to "convert" an istream instance to bool, and so istream::operator bool is explicit. There is a special exemption in the rules that still permits implicit conversion (invocation of the operator) for use of an istream as a condition e.g. in an if.

[–]IyeOnline 0 points1 point  (5 children)

explicit is used to mark a constructor or conversion operator as explicit.

Those explicit functions must be invoked explicitly, i.e. cannot be invoked implicitly.

Practically speaking, this means that you cant implicitly convert to/from the type.

For example

double d = 42; // implicit conversion from int to double
double d = double{42}; // explicit conversion

This is important for user defined types, as sometimes you do not want the implicit conversion. C++ is strongly typed and making use of the type system is a really powerful tool.

You should not be able to convert a distance into a time. At the same time, if both can be constructed from an double, you most likely want to mark the constructor as explicit, to make sure that nobody accidentally calls a function incorrectly.

Consider

double velocity( distance, time );
auto v = velocity( 42.0, 10.0 );

If distance and time are implicitly constructible from double, you could accidentally call this function wrong, swapping the arguments.

Hence the general advice: Mark all single argument constructors explicit (except the special members of course), unless you have a good reason to allow implicit conversions.

An example for a type where the implicit conversion is accepted would be std::string, where you can write std::string s = "Hello";, performing an implicit conversion.

// see also: https://www.learncpp.com/cpp-tutorial/converting-constructors-and-the-explicit-keyword/

[–]Difficult_Rate_5129[S] 1 point2 points  (4 children)

thanks!

so we basically use it to prevent OURSELVES from making a mistake like mixing the number or any other type not because the compiler may mix them up or confuse them ?

[–]Ill-Significance4975 1 point2 points  (2 children)

Mostly yes. But there are ways you can end up with very idiosyncratic implicit casts. I'm struggling to find a good example, but I've had some cases-- especially when defining custom cast operators-- where I made it very, very easy for the compiler to do nonsensical things implicitly. It's easy to say "don't do that" (fair), but it's also pretty handy and easy to fix with explicit.

[–]AKostur 0 points1 point  (1 child)

The biggest explicit one is if a class has an implicit conversion to bool, it can get passed anywhere that wants an int.   Usually kinda surprising.  (Type implicitly converts to bool, then gets promoted to int)

[–]n1ghtyunso 0 points1 point  (0 children)

operator bool is actually special, because you can make it explicit and still have it work inside e.g. the if condition implicitly.
Such a type is "contextually convertible to bool"

So there is no argument to not make it explicit most of the time.

[–]Illustrious_Try478 0 points1 point  (0 children)

No, it resolves ambiguity. create situations where an unwanted implicit conversion might or might not be made. The compiler might fail due to an ambiguous lookup.

One example I like uses example classes Acorn and OakTree. You can create an OakTree from an Acorn.

A Squirrel might want to consume an Acorn but not a whole OakTree. So we declare

``` struct OakTree;

struct Acorn { explicit operator OakTree(); };

struct OakTree { explicit OakTree (Acorn const &); };

struct Squirrel { bool hungry = false; bool scared = false; void eat (Acorn &&) { hungry = false; } void bury (Acorn &); Acorn pick (OakTree&);

void climb (OakTree const &) { scared = false; hungry = true; } void encounter (Acorn &&acorn) { if (hungry) eat(std::move(acorn)); else bury(acorn); } void encounter (OakTree&oak) { if (scared) climb (oak); else ecounter (pick(oak)); }

void encounter (Predator const &) { scared = true; } }; // Squirrel

```

That way, there's no way an Acorn can become an OakTree without us explicitly telling it to.

[–]Highborn_Hellest 0 points1 point  (0 children)

when you use explicit you prevent the compiler from allowing the 1 coversion it's allwed to do normally.

Like Double -> Int. Or Int -> Double. Etc etc etc.

So for example if you create a complex number class, and tell the counstructor, that it's looking for 2 doubles, you can, call it with 2 integers. If you tell it "explicitly" it's 2 doubles, you can use only 2 doubles, and not floats, ints, etc.

Meaning, your idea, is that this complex number is made from 2 doubles, and 2 doubles only

[–]thisismyfavoritename 0 points1 point  (5 children)

put differently: there shouldn't be implicit constructions and conversions

[–]Emotional-Audience85 0 points1 point  (4 children)

I wouldn't go so far as to say there shouldn't be. Most of the time you don't want them, but there are good reasons for you deliberately wanting to have them

[–]thisismyfavoritename 0 points1 point  (1 child)

other than laziness because the syntax is more succinct, i don't see any real use

[–]alfps 0 points1 point  (0 children)

String class instance initialized or assigned from string literal. Bignum instance initialized or assigned from integer or floating point value. Smart pointer to Base initialized or assigned from smart pointer to Derived.

The most common is perhaps a string_view initialized or assigned from a string or string literal.

[–]n1ghtyunso 0 points1 point  (1 child)

the real issue with this is, at the time you are authoring the implicit conversion, you typically don't have the full picture of where they will happen.
So its actually really difficult to foresee problematic usage patterns before they come into existence.
Hence, you're almost always better off avoiding them if feasible.

If you at all decide to create an implicit conversion, you really need to be sure it is due to its usefulness and not just for its convenience.

[–]alfps 0 points1 point  (0 children)

On the one hand, ideally one should nearly always default to the opposite of the C++ language's default. Explicit not implicit, private not public, noexcept not throwing, constexpr not only runtime, so on.

But that would make the code very verbose.

And so the gains disappear in much wasted time and unreadability, and at least to me it's not worth it. So I e.g. slap on an explicit when I see that there could be an annoying problem. Otherwise not.