Skip to content

Use explicit(bool) unconditionally #1301

@StephanTLavavej

Description

@StephanTLavavej

At our request, C1XX, Clang, and EDG support C++20 explicit(bool) unconditionally - i.e. in C++14/17 modes. (Like if constexpr, C1XX and Clang emit Future Technology warnings that can be suppressed.) This allows us to dramatically simplify pair, tuple, and optional.

We've requested similar support from NVIDIA's CUDA compiler. When this is available, and when MSVC can begin requiring that version of CUDA at a minimum, we should update the STL to remove our fallback codepaths.

Product code examples:

STL/stl/inc/yvals_core.h

Lines 371 to 380 in 5f3e912

// Controls whether the STL uses "conditional explicit" internally
#ifndef _HAS_CONDITIONAL_EXPLICIT
#ifdef __cpp_conditional_explicit
#define _HAS_CONDITIONAL_EXPLICIT 1
#elif defined(__CUDACC__) || defined(__INTEL_COMPILER)
#define _HAS_CONDITIONAL_EXPLICIT 0 // TRANSITION, CUDA/ICC
#else // vvv C1XX or Clang or IntelliSense vvv
#define _HAS_CONDITIONAL_EXPLICIT 1
#endif // ^^^ C1XX or Clang or IntelliSense ^^^
#endif // _HAS_CONDITIONAL_EXPLICIT

STL/stl/inc/utility

Lines 184 to 207 in 5f3e912

#if _HAS_CONDITIONAL_EXPLICIT
template <class _Other1, class _Other2,
enable_if_t<conjunction_v<is_constructible<_Ty1, _Other1>, is_constructible<_Ty2, _Other2>>, int> = 0>
constexpr explicit(!conjunction_v<is_convertible<_Other1, _Ty1>, is_convertible<_Other2, _Ty2>>)
pair(_Other1&& _Val1, _Other2&& _Val2) noexcept(
is_nothrow_constructible_v<_Ty1, _Other1>&& is_nothrow_constructible_v<_Ty2, _Other2>) // strengthened
: first(_STD forward<_Other1>(_Val1)), second(_STD forward<_Other2>(_Val2)) {}
#else // ^^^ _HAS_CONDITIONAL_EXPLICIT ^^^ / vvv !_HAS_CONDITIONAL_EXPLICIT vvv
template <class _Other1, class _Other2,
enable_if_t<conjunction_v<is_constructible<_Ty1, _Other1>, is_constructible<_Ty2, _Other2>,
is_convertible<_Other1, _Ty1>, is_convertible<_Other2, _Ty2>>,
int> = 0>
constexpr pair(_Other1&& _Val1, _Other2&& _Val2) noexcept(
is_nothrow_constructible_v<_Ty1, _Other1>&& is_nothrow_constructible_v<_Ty2, _Other2>) // strengthened
: first(_STD forward<_Other1>(_Val1)), second(_STD forward<_Other2>(_Val2)) {}
template <class _Other1, class _Other2,
enable_if_t<conjunction_v<is_constructible<_Ty1, _Other1>, is_constructible<_Ty2, _Other2>,
negation<conjunction<is_convertible<_Other1, _Ty1>, is_convertible<_Other2, _Ty2>>>>,
int> = 0>
constexpr explicit pair(_Other1&& _Val1, _Other2&& _Val2) noexcept(
is_nothrow_constructible_v<_Ty1, _Other1>&& is_nothrow_constructible_v<_Ty2, _Other2>) // strengthened
: first(_STD forward<_Other1>(_Val1)), second(_STD forward<_Other2>(_Val2)) {}
#endif // ^^^ !_HAS_CONDITIONAL_EXPLICIT ^^^

Self-contained test case demonstrating C1XX, Clang, and EDG behavior:

C:\Temp>type explicit.cpp
#include <type_traits>
using namespace std;

#ifdef __clang__
#pragma clang diagnostic ignored "-Wc++20-extensions" // warning: explicit(bool) is a C++20 extension
#else
#pragma warning(disable: 5053) // support for 'explicit(<expr>)' in C++17 and earlier is a vendor extension
#endif

struct No {};
struct Im {};
struct Ex {};

struct A {
    A(Im) {}
    explicit A(Ex) {}
};

template <typename T, typename Arg>
constexpr bool NotConstructible = !is_constructible_v<T, Arg> && !is_convertible_v<Arg, T>;

template <typename T, typename Arg>
constexpr bool ImplicitlyConstructible = is_constructible_v<T, Arg> && is_convertible_v<Arg, T>;

template <typename T, typename Arg>
constexpr bool ExplicitlyConstructible = is_constructible_v<T, Arg> && !is_convertible_v<Arg, T>;

static_assert(NotConstructible<A, No>, "BOOM NotConstructible");
static_assert(ImplicitlyConstructible<A, Im>, "BOOM ImplicitlyConstructible");
static_assert(ExplicitlyConstructible<A, Ex>, "BOOM ExplicitlyConstructible");


template <typename T1, typename T2> struct OldPair {
    template <typename U1, typename U2,
        enable_if_t<conjunction_v<is_constructible<T1, const U1&>, is_constructible<T2, const U2&>,
            is_convertible<const U1&, T1>, is_convertible<const U2&, T2>>, int> = 0>
        OldPair(const OldPair<U1, U2>&) {}

    template <typename U1, typename U2,
        enable_if_t<conjunction_v<is_constructible<T1, const U1&>, is_constructible<T2, const U2&>,
            negation<conjunction<is_convertible<const U1&, T1>, is_convertible<const U2&, T2>>>>, int> = 0>
        explicit OldPair(const OldPair<U1, U2>&) {}
};

static_assert(NotConstructible<OldPair<A, A>, const OldPair<No, No>&>, "OldPair BOOM 1");
static_assert(NotConstructible<OldPair<A, A>, const OldPair<No, Im>&>, "OldPair BOOM 2");
static_assert(NotConstructible<OldPair<A, A>, const OldPair<Im, No>&>, "OldPair BOOM 3");
static_assert(ImplicitlyConstructible<OldPair<A, A>, const OldPair<Im, Im>&>, "OldPair BOOM 4");
static_assert(ExplicitlyConstructible<OldPair<A, A>, const OldPair<Im, Ex>&>, "OldPair BOOM 5");
static_assert(ExplicitlyConstructible<OldPair<A, A>, const OldPair<Ex, Im>&>, "OldPair BOOM 6");
static_assert(ExplicitlyConstructible<OldPair<A, A>, const OldPair<Ex, Ex>&>, "OldPair BOOM 7");


template <typename T1, typename T2> struct NewPair {
    template <typename U1, typename U2,
        enable_if_t<conjunction_v<is_constructible<T1, const U1&>, is_constructible<T2, const U2&>>, int> = 0>
        explicit(!is_convertible_v<const U1&, T1> || !is_convertible_v<const U2&, T2>)
        NewPair(const NewPair<U1, U2>&) {}
};

static_assert(NotConstructible<NewPair<A, A>, const NewPair<No, No>&>, "NewPair BOOM 1");
static_assert(NotConstructible<NewPair<A, A>, const NewPair<No, Im>&>, "NewPair BOOM 2");
static_assert(NotConstructible<NewPair<A, A>, const NewPair<Im, No>&>, "NewPair BOOM 3");
static_assert(ImplicitlyConstructible<NewPair<A, A>, const NewPair<Im, Im>&>, "NewPair BOOM 4");
static_assert(ExplicitlyConstructible<NewPair<A, A>, const NewPair<Im, Ex>&>, "NewPair BOOM 5");
static_assert(ExplicitlyConstructible<NewPair<A, A>, const NewPair<Ex, Im>&>, "NewPair BOOM 6");
static_assert(ExplicitlyConstructible<NewPair<A, A>, const NewPair<Ex, Ex>&>, "NewPair BOOM 7");

int main() { }
C:\Temp>cl /EHsc /nologo /W4 /std:c++14 explicit.cpp
explicit.cpp

C:\Temp>cl /EHsc /nologo /W4 /std:c++14 /c /BE explicit.cpp
explicit.cpp

C:\Temp>clang-cl /EHsc /nologo /W4 /std:c++14 explicit.cpp

C:\Temp>

Metadata

Metadata

Assignees

No one assigned

    Labels

    fixedSomething works now, yay!throughputMust compile faster

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions