NKAR
NKAR

Reputation: 348

what is the usecase for explicit (bool)

C++20 introduced explicit (bool) which conditionally selects at compile-time whether a constructor is made explicit or not.

Below is an example which I found here.

struct foo {

  // Specify non-integral types (strings, floats, etc.) require explicit construction.

  template <typename T>

  explicit(!std::is_integral_v<T>) foo(T) {}

};

foo a = 123; // OK

foo b = "123"; // ERROR: explicit constructor is not a candidate (explicit specifier evaluates to true)

foo c {"123"}; // OK

Can anyone tell me any other usecase for explicit (bool) other than using std::is_integral?

Upvotes: 24

Views: 3398

Answers (3)

Barry
Barry

Reputation: 303487

The motivation itself can be seen in the paper.

There is a need to make constructors conditionally explicit. That is, you want:

pair<string, string> safe() {
    return {"meow", "purr"}; // ok
}

pair<vector<int>, vector<int>> unsafe() {
    return {11, 22}; // error
}

The former is fine, those constructors are implicit. But the latter would be bad, those constructors are explicit. With C++17 (or C++20 with concepts), the only way to make this work is to write two constructors - one explicit and one not:

template <typename T1, typename T2>
struct pair {
    template <typename U1=T1, typename U2=T2,
        std::enable_if_t<
            std::is_constructible_v<T1, U1> &&
            std::is_constructible_v<T2, U2> &&
            std::is_convertible_v<U1, T1> &&
            std::is_convertible_v<U2, T2>
        , int> = 0>
    constexpr pair(U1&&, U2&& );

    template <typename U1=T1, typename U2=T2,
        std::enable_if_t<
            std::is_constructible_v<T1, U1> &&
            std::is_constructible_v<T2, U2> &&
            !(std::is_convertible_v<U1, T1> &&
              std::is_convertible_v<U2, T2>)
        , int> = 0>
    explicit constexpr pair(U1&&, U2&& );    
};  

These are almost entirely duplicated - and the definitions of these constructors would be identical.

With explicit(bool), you can just write a single constructor - with the conditionally explicit part of the construction localized to just the explicit-specifier:

template <typename T1, typename T2>
struct pair {
    template <typename U1=T1, typename U2=T2,
        std::enable_if_t<
            std::is_constructible_v<T1, U1> &&
            std::is_constructible_v<T2, U2>
        , int> = 0>
    explicit(!std::is_convertible_v<U1, T1> ||
        !std::is_convertible_v<U2, T2>)
    constexpr pair(U1&&, U2&& );   
};

This matches intent better, is much less code to write, and is less work for the compiler to do during overload resolution (since there are fewer constructors to have to pick between).

Upvotes: 23

Jarod42
Jarod42

Reputation: 217970

Another possible usage I see is with variadic template:

It is generally good, by default, to have explicit for constructor with only one argument (unless the conversion is desired).

so

struct Foo
{
    template <typename ... Ts>
    explicit(sizeof...(Ts) == 1) Foo(Ts&&...);

    // ...
};

Upvotes: 7

ShadowRanger
ShadowRanger

Reputation: 155516

I could see a use case for requiring explicit conditionally when the input might be a view-like type (raw pointer, std::string_view) that the new object will hold on to after the call (only copying the view, not what it refers to, remaining dependent on the lifetime of the viewed object), or it might be a value-like type (takes ownership of a copy, with no external lifetime dependencies).

In a situation like that, the caller is responsible for keeping the viewed object alive (the callee owns a view, not the original object), and the conversion should not be done implicitly, because it makes it too easy for the implicitly created object to outlive the object it views. By contrast, for value types, the new object will receive its own copy, so while the copy might be costly, it won't make the code wrong if an implicit conversion occurs.

Upvotes: 0

Related Questions