Let's try C++20 | explicit(bool)
Tags: cpp, programming, todayilearned
Published: 2020-12-07 07:52:43

From the many changes of C++20, let's focus today on `explicit(bool)`. This feature is described in [P0892R2](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0892r2.html). The [`explicit` keyword](https://en.cppreference.com/w/cpp/language/explicit) avoids implicit conversions by unexpectedly call a constructor. With C++20, it is now possible to have boolean condition as a "parameter" to the keyword.

## How it works

This simple code makes it easy to understand how `explicit(bool)` works:

```cpp
constexpr bool ENABLE_EXPLICIT = false;

struct Foo {
    explicit(ENABLE_EXPLICIT)
    Foo(int) {}
};

Foo a = 1;
```

This code compiles fine, but change `ENABLE_EXPLICIT` to `true` and you will get an error with clang-10.0.0:

    error: no viable conversion from 'int' to 'Foo'
    note: explicit constructor is not a candidate (explicit specifier evaluates to true)

`explicit(bool)` is that simple.

## Purpose

OK but the code above is quite non-sense. Why would one need `explicit(bool)`?

In template types, it is often desirable to make constructors explicit or implicit based on certain properties of the template parameters. The proposal P0892R2 shows an example with `std::pair`:

```cpp
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&& );
};
```

Depending on what are T1, T2, U1 and U2:

- It may or may not be possible to create an instance of `std::pair<T1, T2>` from two objects of types `U1` and `U2`
- The constructor may be explicit or implicit.

Thanks to the new `explicit(bool)` feature, the code above can be rewritten as:

```cpp
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&& );
};
```

The code is clearly improved. First, there is only one constructor so it avoids code duplication. Secondly, the condition that makes the constructor explicit is separated from the condition that makes the constructor viable. See "PS" at the end of this article for more explanations about these conditions.

## Other examples

[This answer by Jarod42 on stackoverflow](https://stackoverflow.com/a/60650417/12342718) shows that it is possible to make variadic constructors explicit only if there is one parameter in the pack:

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

Foo good = {1,2}; // OK
Foo bad = {1}; // error: chosen constructor is explicit in copy-initialization
```

In their article "[C++20’s Conditionally Explicit Constructors](https://devblogs.microsoft.com/cppblog/c20s-conditionally-explicit-constructors/)", Sy Brand from Microsoft uses `explicit(bool)` to write wrappers for strings and takes advanges of `explicit(bool)` to simplify their code, in a very similar manner as in the example of the proposal. They invoke the [principle of least astonishment](https://en.wikipedia.org/wiki/Principle_of_least_astonishment) as a motivation for wrappers to behave in a similar way as the wrapped types.

## Conclusion

`explicit(bool)` is clearly a feature for people who write template types. It is likely that you won't use it in you everyday life but it may help you understand code written by others.

## PS

I think it is interesting to understand why the constructor of `pair` is declared as:

```cpp
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&& );
};
```

There are 2 things to understand: the usage of `std::enabled_if_t` and the condition in `explicit`. Buckle up!

### 1) Why is std::enabled_if_t used here?

Let's consider a simple code like this:

```cpp
template <typename T>
struct Foo {
    template<typename U>
    Foo(U u) : t(u) {}

    T t;
};

Foo<int> good = 1;
Foo<int> bad = "1";
```

This code generates of an error for `bad`:

    error: cannot initialize a member subobject of type 'int' with an lvalue of type 'const char *'
    note: in instantiation of function template specialization 'Foo<int>::Foo<const char *>'

The error occurs in the "body" of the constructor. It would be nice to catch the error earlier by rejecting the call to the constructor. This can be done thanks to `std::enable_if`:

```cpp
#include <type_traits>

template <typename T>
struct Foo {
    template<typename U,
             std::enable_if_t<std::is_constructible_v<T, U>, int> = 0>
    Foo(U u) : t(u) {}

    T t;
};
```

The error becomes:

    error: no viable conversion from 'const char [2]' to 'Foo<int>'
    note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'const char [2]' to 'const Foo<int> &'
    note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'const char [2]' to 'Foo<int> &&'
    note: candidate template ignored: requirement 'std::is_constructible_v<int, const char *>' was not satisfied [with U = const char *]

Noice! 😎

Let's understand how this works.

`template<typename U, std::enable_if_t<std::is_constructible_v<T, U>, int> = 0>` is equivalent to `template<typename U, SOMETHING = 0>` where `SOMETHING` is `std::enable_if_t<std::is_constructible_v<T, U>, int>`.

[cppreference explains](https://en.cppreference.com/w/cpp/types/enable_if) how `std::enable_if` works:

> Defined in header `<type_traits>`
>
>```cpp
> template< bool `B`, class T = void >
> struct enable_if;
>```
>
> If B is true, `std::enable_if` has a public member typedef type, equal to `T`; otherwise, there is no member typedef.

In our case, the boolean condition `B` is `std::is_constructible_v<T, U>` and the typedef type is `int`.

Hence:

- If `T` can be [constructed](https://en.cppreference.com/w/cpp/types/is_constructible) from `U`, then `SOMETHING` is `int`. The constructor has a second template parameter of type `int`, which is defaulted to 0, and the constructor can be called.
- Otherwise, the template definition is ill-formed, and [thanks to SFINAE](https://en.cppreference.com/w/cpp/language/sfinae), this constructor is removed from the overloads. The other overloads are the compiler-defined move and copy constructors but there are not acceptable neither.

At the end of the day, there is a viable conversion from `1` to `Foo` but there isn't from `"1"` to `Foo` and we get a clear error.

We can now get back to `std::pair`'s constructor and easily understand it: it's the same as `Foo` but with an additional template type.

### 2) How is the constructor made explicit?

`std::pair`'s constructor is explicit if the following condition is true: `!std::is_convertible_v<U1, T1> || !std::is_convertible_v<U2, T2>`. It means that if `U1` and/or `U2` cannot be implicitly converted to `T1` and `T2` respectively, then the constructor is explicit.

This behavior follows the [principle of least astonishment](https://en.wikipedia.org/wiki/Principle_of_least_astonishment): it would be astonishing if a pair of `{U1, U2}` could be implicitly converted to pair of `{T1, T2}` if `U1` and/or `U2` cannot be converted to `T1` and `T2` implicitly.

Example with a custom `Pair` type:

```cpp
template <typename T>
struct Foo {
    explicit Foo(T) {}
};

template <typename T>
struct Pair {
    template <typename U>
    Pair(U, U) {}
};

Pair<Foo<int>> pair = {1, 2}; // OK
Foo<int> foo1 = 1; // error: no viable conversion from 'int' to 'Foo<int>'
Foo<int> foo2 = 2; // error: no viable conversion from 'int' to 'Foo<int>'
```

So... We can create a pair of `Foo<int>` from two `int`s but we can't create two `Foo`s from two `int`s? It's quite weird and we can use `explicit(bool)` (as in `std::pair`) to avoid that:

```cpp
template <typename T>
struct Pair {
    template <typename U>
    explicit(not std::is_convertible_v<U, T>)
    Pair(U, U) {}
};

Pair<Foo<int>> pair = {1, 2}; // error: chosen constructor is explicit in copy-initialization
```

If we remove the `explicit` keyword from the definition of `Foo::Foo(T)`, the error disappears and we can create `pair` from `{1, 2}`.

### That's it!

I hope you enjoyed this trip into the details of the declaration of `std::pair`'s constructor as much as I have! 😁