14

I was trying to understand the usage of explicit keyword in c++ and looked at this question on SO What does the explicit keyword mean in C++?

However, examples listed there (actually both top two answers) are not very clear regarding the usage. For example,

// classes example
#include <iostream>
using namespace std;

class String {
public:
    explicit String(int n); // allocate n bytes to the String object
    String(const char *p); // initializes object with char *p
};

String::String(int n)
{
    cout<<"Entered int section";
}

String::String(const char *p)
{
    cout<<"Entered char section";
}

int main () {

    String mystring('x');
    return 0;
}

Now I have declared String constructor as explicit, but lets say if I dont list it as explicit, if I call the constructor as,

String mystring('x');

OR

String mystring = 'x';

In both cases, I will enter the int section. Until and unless I specify the type of value, it defaults to int. Even if i get more specific with arguments, like declare one as int, other as double, and not use explicit with constructor name and call it this way

String mystring(2.5);

Or this way

String mystring = 2.5;

It will always default to the constructor with double as argument. So, I am having hard time understanding the real usage of explicit. Can you provide me an example where not using explicit will be a real failure?

Community
  • 1
  • 1
SandBag_1996
  • 1,570
  • 3
  • 20
  • 50
  • 2
    Are you expecting `String('x')` to call `String(const char*)`? `'x'` is a `char`, not a `char` pointer. Anyway, you're only showing *explicit* constructions of a `String`, so `explicit` doesn't make a difference. Try calling a function that takes a `String` with an `int`, and it won't work, because the conversion `int` → `String` can't be made *implicitly*. – Biffen Mar 30 '16 at 14:00
  • `String mystring = 'x';` [does _not_ work](http://coliru.stacked-crooked.com/a/79af8ce27adf8a61). – cpplearner Mar 30 '16 at 14:09
  • @cpplearner I am aware of that, thats why I indicated that I dont use the word explicit. In your example, you are using explicit – SandBag_1996 Mar 30 '16 at 14:10
  • @Biffen can you expand your argument with an example code – SandBag_1996 Mar 30 '16 at 14:20
  • @UnderDog Which argument? If you mean calling a function that takes a `String`, then see Jerry Coffin's answer. – Biffen Mar 30 '16 at 14:21
  • @biffen I looked at it but I am already aware of that usage. And thats the point of my whole question, I am aware that if I declare the constructor as explicit, and then I call a constructor with argument as same type as that of the constructor that has been declared as explicit, the type of definition I do matters, but if there is no explicit, I can define it anyhow and I would always get the right behavior. I am looking for examples where not using explicit fails – SandBag_1996 Mar 30 '16 at 14:25
  • @UnderDog Not using `explicit` won't *fail*, but it can give undesired behaviour. – Biffen Mar 30 '16 at 14:26
  • @Biffen But Jerry coffin's examples doesnt show that undesired behavior or may be I am not understanding it correctly – SandBag_1996 Mar 30 '16 at 14:28
  • @UnderDog Well, if I were to write a string class, I would *not* want it to be implicitly convertible from an `int`, because the conversion is not *obvious* (whereas that from `const char*` *is*). Does it turn the number into a string? Does it get a string from some numbered reference? I'd rather force the user to be *explicit* when they want to use that constructor. – Biffen Mar 30 '16 at 14:43

1 Answers1

13

explicit is intended to prevent implicit conversions. Anytime you use something like String(foo);, that's an explicit conversion, so using explicit won't change whether it succeeds or fails.

Therefore, let's look at a scenario that does involve implicit conversion. Let's start with your String class:

class String {
public:
    explicit String(int n); // allocate n bytes to the String object
    String(const char *p); // initializes object with char *p
};

Then let's define a function that receives a parameter of type String (could also be String const &, but String will do for the moment):

int f(String);

Your constructors allow implicit conversion from char const *, but only explicit conversion from int. This means if I call:

f("this is a string");

...the compiler will generate the code to construct a String object from the string literal, and then call f with that String object.

If, however, you attempt to call:

f(2);

It will fail, because the String constructor that takes an int parameter has been marked explicit. That means if I want to convert an int to a String, I have to do it explicitly:

f(String(2));

If the String(char const *); constructor were also marked explicit, then you wouldn't be able to call f("this is a string") either--you'd have to use f(String("this is a string"));

Note, however, that explicit only controls implicit conversion from some type foo to the type you defined. It has no effect on implicit conversion from some other type to the type your explicit constructor takes. So, your explicit constructor that takes type int will still take a floating point parameter:

f(String(1.2))

...because that involves an implicit conversion from double to int followed by an explicit conversion from int to String. If you want to prohibit a conversion from double to String, you'd do it by (for example) providing an overloaded constructor that takes a double, but then throws:

String(double) { throw("Conversion from double not allowed"); }

Now the implicit conversion from double to int won't happen--the double will be passed directly to your ctor without conversion.

As to what using explicit accomplishes: the primary point of using explicit is to prevent code from compiling that would otherwise compile. When combined with overloading, implicit conversions can lead to some rather strange selections at times.

It's easier to demonstrate a problem with conversion operators rather than constructors (because you can do it with only one class). For example, let's consider a tiny string class fairly similar to a lot that were written before people understood as much about how problematic implicit conversions can be:

class Foo {
    std::string data;
public:
    Foo(char const *s) : data(s) { }
    Foo operator+(Foo const &other) { return (data + other.data).c_str(); }

    operator char const *() { return data.c_str(); }
};

(I've cheated by using std::string to store the data, but the same would be true if I did like they did and stored a char *, and used new to allocate the memory).

Now, this makes things like this work fine:

Foo a("a");
Foo b("b");

std::cout << a + b;

...and, (of course) the result is that it prints out ab. But what happens if the user makes a minor mistake and types - where they intended to type +?

That's where things get ugly--the code still compiles and "works" (for some definition of the word), but prints out nonsense. In a quick test on my machine, I got -24, but don't count on duplicating that particular result.

The problem here stems from allowing implicit conversion from String to char *. When we try to subtract the two String objects, the compiler tries to figure out what we meant. Since it can't subtract them directly, it looks at whether it can convert them to some type that supports subtraction--and sure enough, char const * supports subtraction, so it converts both our String objects to char const *, then subtracts the two pointers.

If we mark that conversion as explicit instead:

explicit operator char const *() { return data.c_str(); }

...the code that tries to subtract two String objects simply won't compile.

The same basic idea can/does apply to explicit constructors, but the code to demonstrate it gets longer because we generally need at least a couple of different classes involved.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • I am not sure if you are correct on conversion from doubt to int. If I pass in a double value to String object, it will directly go to the constructor which expects double as argument (if there is any), irrespective of explicit. – SandBag_1996 Mar 30 '16 at 14:18
  • @UnderDog: Yes and no. Yes, that's the one overload resolution will select. But, if it's marked explicit, and you're trying to use it to do an implicit conversion, then the code won't compile. – Jerry Coffin Mar 30 '16 at 15:35
  • I understand that and I agree. But my question is not regarding that. I am more interested to know about the cases or an example code, where not using explicit causes a failure or an undesired behavior, because so far with the explanation that I have understood, I dont see much worth in using explicit. – SandBag_1996 Mar 30 '16 at 15:38
  • @UnderDog: I've added an example. – Jerry Coffin Mar 30 '16 at 16:34