johnbakers
johnbakers

Reputation: 24760

Prevent undesired conversion in constructor

According to here, explicit:

Specifies constructors and conversion operators (since C++11) that don't allow implicit conversions or copy-initialization.

Thus, are these two techniques identical?

struct Z {
        // ...
        Z(long long);     // can initialize with a long long
        Z(long) = delete; // but not anything smaller
};

struct Z {
        // ...
        explicit Z(long long);     // can initialize ONLY with a long long
};

Upvotes: 19

Views: 5277

Answers (5)

Andreas DM
Andreas DM

Reputation: 10998

They are not the same.

From the standard working draft n4296:

12.3.1 - [class.conv.ctor]:
1 A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters to the type of its class. Such a constructor is called a converting constructor.

2 An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (8.5) or where casts (5.2.9, 5.4) are explicitly used. A default constructor may be an explicit constructor; such a constructor will be used to perform default-initialization or valueinitialization (8.5).

Followed by an example of each one respectively:

struct X {
    X(int);
    X(const char*, int =0);
    X(int, int);
};

void f(X arg) {
    X a = 1;        // a = X(1)
    X b = "Jessie"; // b = X("Jessie",0)
    a = 2;          // a = X(2)
    f(3);           // f(X(3))
    f({1, 2});      // f(X(1,2))
}

With explicit constructor:

struct Z {
    explicit Z();
    explicit Z(int);
    explicit Z(int, int);
};

Z a;                      // OK: default-initialization performed
Z a1 = 1;                 // error: no implicit conversion
Z a3 = Z(1);              // OK: direct initialization syntax used
Z a2(1);                  // OK: direct initialization syntax used
Z* p = new Z(1);          // OK: direct initialization syntax used
Z a4 = (Z)1;              // OK: explicit cast used
Z a5 = static_cast<Z>(1); // OK: explicit cast used
Z a6 = { 3, 4 };          // error: no implicit conversion

Upvotes: 1

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275395

explicit blocks implicit conversion to your type.

Your =delete technique blocks implicit conversion from long to long long.

These are almost unrelated.

There are 4 cases that illustrate the difference:

Z z = 1L;
Z z = 1LL;

is an implicit conversion from long and long long to Z.

Z z = Z(1L);
Z z = Z(1LL);

is an explicit conversion from long and long long to Z.

explicit Z(long long) blocks:

Z z = 1L;
Z z = 1LL;

while Z(long)=delete blocks:

Z z = 1L;
Z z = Z(1L);

explicit Z(long long) allows Z z = Z(1L) because the conversion from long to long long is implicit, but unrelated to the explicit conversion to Z that happens afterwards.

Note that a mixture of explicit and =delete leaves only Z z=Z(1LL) as valid among your 4 versions.

(the above presumes a valid copy or move ctor; if not, replace Z z=Z(...) with Z z(...) and the same conclusions result).

Upvotes: 6

Barry
Barry

Reputation: 303057

No, they're not the same. explicit disallows implicit conversions to that type if that constructor is selected - implicit conversions in arguments don't matter. delete disallows any construction if that constructor is selected, and can be used to disallow implicit argument conversion.

So for instance:

struct X {
    explicit X(int ) { }
};

void foo(X ) { }

foo(4);      // error, because X's constructor is explicit
foo(X{3});   // ok
foo(X{'3'}); // ok, this conversion is fine

That is separate from deleteing a constructor:

struct Y {
    Y(int ) { }
    Y(char ) = delete;
};

void bar(Y ) { }

bar(4);      // ok, implicit conversion to Y since this constructor isn't explicit
bar('4');    // error, this constructor is deleted
bar(Y{'4'}); // error, doesn't matter that we're explicit

The two techniques are also orthogonal. If you want a type to not be implicitly-convertible and only constructible from exactly an int, you can do both:

struct W {
    explicit W(int ) { }

    template <class T>
    W(T ) = delete;
};

void quux(W );

quux(4);      // error, constructor is explicit
quux('4');    // error, constructor is deleted
quux(4L);     // error, constructor is deleted
quux(W{'4'}); // error, constructor is deleted
quux(W{5});   // ok

Upvotes: 28

eerorika
eerorika

Reputation: 238351

They're not identical.

Z z = 1LL;

The above works with the non-explicit version, but not with the explicit version.

Declaring constructor of Z explicit doesn't prevent conversion of the constructor argument from another type. It prevents the conversion from the argument to Z without calling the constructor explicitly.

Below is an example of explicit constructor call.

Z z = Z(1LL);

Upvotes: 19

Francis Straccia
Francis Straccia

Reputation: 916

struct Zb {
        Zb(long long)
        {};     // can initialize with a long long
        Zb(long) = delete; // but not anything smaller
    };

struct Za {
        // ...
        explicit Za(long long)
        {};     // can initialize ONLY with a long long
    };

int main()
{
    Za((long long)10);  // works
    Za((long)10);       // works    

    Zb((long long)10);  // works
    Zb((long)10);       // does not work

    return 0;
}

Your example requires explicit deleting.

Live: http://cpp.sh/4sqb

Upvotes: 2

Related Questions