/*
 * Copyright (c) 2022 NVIDIA Corporation
 *
 * Licensed under the Apache License Version 2.0 with LLVM Exceptions
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *   https://llvm.org/LICENSE.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#pragma once

// IWYU pragma: always_keep

#if __cplusplus < 202002L
#  if defined(_MSC_VER) && !defined(__clang__)
#    error This library requires the use of C++20. Use /Zc:__cplusplus to enable __cplusplus conformance.
#  else
#    error This library requires the use of C++20.
#  endif
#endif

#if defined(_MSC_VER) && !defined(__clang__) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL)
#  error This library requires the use of the new conforming preprocessor enabled by /Zc:preprocessor.
#endif

#include "__preprocessor.hpp"

#if __has_include(<version>)
#  include <version>
#else
#  include <ciso646>  // For stdlib feature-test macros when <version> is not available
#endif

#include <cassert>
#include <cstdlib>
#include <type_traits>  // IWYU pragma: keep
#include <utility>      // IWYU pragma: keep for std::unreachable

// When used with no arguments, these macros expand to 1 if the current
// compiler corresponds to the macro name; 0, otherwise. When used with arguments,
// they expand to the arguments if if the current compiler corresponds to the
// macro name; nothing, otherwise.
#if defined(__NVCC__)
#  define STDEXEC_NVCC()       1
#  define STDEXEC_NVCC_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__)
#elif defined(__EDG__)
#  define STDEXEC_EDG()       1
#  define STDEXEC_EDG_VERSION __EDG_VERSION__
#  if defined(__NVCOMPILER)
#    define STDEXEC_NVHPC()       1
#    define STDEXEC_NVHPC_VERSION (__NVCOMPILER_MAJOR__ * 100 + __NVCOMPILER_MINOR__)
#  endif
#  if defined(__INTELLISENSE__)
#    define STDEXEC_INTELLISENSE() 1
#    define STDEXEC_MSVC_HEADERS() 1
#  endif
#elif defined(__clang__)
#  define STDEXEC_CLANG()       1
#  define STDEXEC_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
#  if defined(_MSC_VER)
#    define STDEXEC_CLANG_CL() 1
#  endif
#  if defined(__apple_build_version__)
#    define STDEXEC_APPLE_CLANG()       1
// Apple clang version is encoded as major * 1000000 + minor * 1000 + patch. We ignore the patch
// version here, as it is not relevant for the purposes of this library.
#    define STDEXEC_APPLE_CLANG_VERSION (__apple_build_version__ / 1000)
#  endif
#elif defined(__GNUC__)
#  define STDEXEC_GCC()       1
#  define STDEXEC_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
#elif defined(_MSC_VER)
#  define STDEXEC_MSVC()         1
#  define STDEXEC_MSVC_HEADERS() 1
#  define STDEXEC_MSVC_VERSION   _MSC_VER
#endif

#ifndef STDEXEC_NVCC
#  define STDEXEC_NVCC() 0
#endif
#ifndef STDEXEC_NVHPC
#  define STDEXEC_NVHPC() 0
#endif
#ifndef STDEXEC_EDG
#  define STDEXEC_EDG() 0
#endif
#ifndef STDEXEC_CLANG
#  define STDEXEC_CLANG() 0
#endif
#ifndef STDEXEC_CLANG_CL
#  define STDEXEC_CLANG_CL() 0
#endif
#ifndef STDEXEC_APPLE_CLANG
#  define STDEXEC_APPLE_CLANG() 0
#endif
#ifndef STDEXEC_GCC
#  define STDEXEC_GCC() 0
#endif
#ifndef STDEXEC_MSVC
#  define STDEXEC_MSVC() 0
#endif
#ifndef STDEXEC_MSVC_HEADERS
#  define STDEXEC_MSVC_HEADERS() 0
#endif
#ifndef STDEXEC_INTELLISENSE
#  define STDEXEC_INTELLISENSE() 0
#endif

// Not all supported compilers have implemented the resolution of CWG 2428 yet.
// https://cplusplus.github.io/CWG/issues/2428.html
#if (STDEXEC_CLANG_VERSION >= 1900) || (STDEXEC_GCC_VERSION >= 1300)                               \
  || (STDEXEC_MSVC_VERSION >= 1944)
#  define STDEXEC_DEPRECATE_CONCEPT(_MSG) [[deprecated(_MSG)]]
#else
#  define STDEXEC_DEPRECATE_CONCEPT(_MSG)
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
#if STDEXEC_MSVC()
#  define STDEXEC_PRAGMA(_ARG) __pragma(_ARG)
#else
#  define STDEXEC_PRAGMA(_ARG) _Pragma(STDEXEC_PP_STRINGIZE(_ARG))
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
#if defined(__CUDACC__) || defined(_NVHPC_CUDA)
#  define STDEXEC_CUDA_COMPILATION() 1
#else
#  define STDEXEC_CUDA_COMPILATION() 0
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
#if defined(__has_attribute)
#  define STDEXEC_HAS_ATTRIBUTE(...) __has_attribute(__VA_ARGS__)
#else
#  define STDEXEC_HAS_ATTRIBUTE(...) 0
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
#if defined(__has_cpp_attribute)
#  define STDEXEC_HAS_CPP_ATTRIBUTE(...) __has_cpp_attribute(__VA_ARGS__)
#else
#  define STDEXEC_HAS_CPP_ATTRIBUTE(...) 0
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
#if STDEXEC_CLANG() && STDEXEC_CUDA_COMPILATION()
#  define STDEXEC_HOST_DEVICE_DEDUCTION_GUIDE __host__ __device__
#else
#  define STDEXEC_HOST_DEVICE_DEDUCTION_GUIDE
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
#if STDEXEC_NVCC()
#  define STDEXEC_EXEC_CHECK_DISABLE STDEXEC_PRAGMA(nv_exec_check_disable)
#else
#  define STDEXEC_EXEC_CHECK_DISABLE
#endif

// The following macros are used to define a namespace alias for the standard library.
// It is used when forward-declaring a standard library type or function, which is not
// portable but sometimes necessary to avoid pulling in a large header when a fwd decl
// would do.
#if defined(_LIBCPP_VERSION)
#  define STDEXEC_NAMESPACE_STD_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD
#  define STDEXEC_NAMESPACE_STD_END   _LIBCPP_END_NAMESPACE_STD
#elif defined(__GLIBCXX__)
#  define STDEXEC_NAMESPACE_STD_BEGIN                                                              \
    namespace std {                                                                                \
      _GLIBCXX_BEGIN_NAMESPACE_VERSION
#  define STDEXEC_NAMESPACE_STD_END                                                                \
    _GLIBCXX_END_NAMESPACE_VERSION                                                                 \
    }
#elif defined(_MSVC_STL_VERSION)
#  define STDEXEC_NAMESPACE_STD_BEGIN _STD_BEGIN
#  define STDEXEC_NAMESPACE_STD_END   _STD_END
#else
#  define STDEXEC_NAMESPACE_STD_BEGIN namespace std {
#  define STDEXEC_NAMESPACE_STD_END   }
#endif

STDEXEC_NAMESPACE_STD_BEGIN
  namespace execution
  {
    namespace parallel_scheduler_replacement
    {}

    namespace [[deprecated("Use the std::execution::parallel_scheduler_replacement namespace "
                           "instead.")]] system_context_replaceability
    {
      using namespace parallel_scheduler_replacement;
    }  // namespace system_context_replaceability
  }  // namespace execution

  namespace this_thread
  {}
STDEXEC_NAMESPACE_STD_END

////////////////////////////////////////////////////////////////////////////////////////////////////
#if !defined(STDEXEC_NAMESPACE)
#  define STDEXEC stdexec
#else
#  define STDEXEC STDEXEC_NAMESPACE
#endif

// Detect if stdexec is being defined within namespace std
#define STDEXEC_NAMESPACE_IS_WITHIN_STD_PROBE_std STDEXEC_PP_PROBE(~, 1)
#define STDEXEC_NAMESPACE_IS_WITHIN_STD()                                                          \
  STDEXEC_PP_CHECK(STDEXEC_PP_CAT(STDEXEC_NAMESPACE_IS_WITHIN_STD_PROBE_, STDEXEC))

#define STDEXEC_NAMESPACE_IS_STD_CHECK_I(_0, _1, ...) STDEXEC_PP_IS_EMPTY(__VA_ARGS__)
#define STDEXEC_NAMESPACE_IS_STD_CHECK(...)           STDEXEC_NAMESPACE_IS_STD_CHECK_I(__VA_ARGS__)
#define STDEXEC_NAMESPACE_IS_STD_0()                  0
#define STDEXEC_NAMESPACE_IS_STD_1()                                                               \
  STDEXEC_NAMESPACE_IS_STD_CHECK(STDEXEC_PP_CAT(STDEXEC_NAMESPACE_IS_WITHIN_STD_PROBE_, STDEXEC))
#define STDEXEC_NAMESPACE_IS_STD()                                                                 \
  STDEXEC_PP_CAT(STDEXEC_NAMESPACE_IS_STD_, STDEXEC_NAMESPACE_IS_WITHIN_STD())()

#if STDEXEC_NAMESPACE_IS_STD()
#  error stdexec cannot be defined directly in namespace std, but a namespace nested inside std is allowed.
#endif

// clang-format off
#if STDEXEC_NAMESPACE_IS_WITHIN_STD()
#  define STDEXEC_P2300_NAMESPACE_BEGIN(...)   STDEXEC_NAMESPACE_STD_BEGIN __VA_OPT__(namespace __VA_ARGS__ {)
#  define STDEXEC_P2300_NAMESPACE_END(...)     __VA_OPT__(}) STDEXEC_NAMESPACE_STD_END
#  define STDEXEC_P2300_DEPRECATED_SYMBOL(...) using __VA_ARGS__;
#else
#  define STDEXEC_P2300_NAMESPACE_BEGIN(...)   namespace STDEXEC {
#  define STDEXEC_P2300_NAMESPACE_END(...)     }
#  define STDEXEC_P2300_DEPRECATED_SYMBOL(...)
#endif
// clang-format on

////////////////////////////////////////////////////////////////////////////////////////////////////
#if __cpp_impl_coroutine >= 201902L && __cpp_lib_coroutine >= 201902L
#  include <coroutine>  // IWYU pragma: keep
#  define STDEXEC_NO_STDCPP_COROUTINES() 0
namespace STDEXEC::__std
{
  // NOLINTBEGIN(misc-unused-alias-decls)
  using std::coroutine_handle;
  using std::suspend_always;
  using std::suspend_never;
  using std::noop_coroutine;
  // NOLINTEND(misc-unused-alias-decls)
}  // namespace STDEXEC::__std
#elif defined(__cpp_coroutines) && __has_include(<experimental/coroutine>)
#  include <experimental/coroutine>  // IWYU pragma: keep
#  define STDEXEC_NO_STDCPP_COROUTINES() 0
namespace STDEXEC::__std
{
  // NOLINTBEGIN(misc-unused-alias-decls)
  using std::experimental::coroutine_handle;
  using std::experimental::suspend_always;
  using std::experimental::suspend_never;
  using std::experimental::noop_coroutine;
  // NOLINTEND(misc-unused-alias-decls)
}  // namespace STDEXEC::__std
#else
#  define STDEXEC_NO_STDCPP_COROUTINES() 1
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
// For portably declaring attributes on functions and types
//   Usage:
//
//   STDEXEC_ATTRIBUTE(attr1, attr2, ...)
//   void foo() { ... }
#define STDEXEC_ATTRIBUTE_I(_ATTR)                       STDEXEC_PP_SWITCH(STDEXEC_ATTRIBUTE, _ATTR)
#define STDEXEC_ATTRIBUTE(...)                           STDEXEC_PP_FOR_EACH(STDEXEC_ATTRIBUTE_I, __VA_ARGS__)

#define STDEXEC_ATTRIBUTE_SWITCH_no_unique_address       STDEXEC_PP_CASE(NO_UNIQUE_ADDRESS)
#define STDEXEC_ATTRIBUTE_SWITCH_always_inline           STDEXEC_PP_CASE(ALWAYS_INLINE)
#define STDEXEC_ATTRIBUTE_SWITCH_empty_bases             STDEXEC_PP_CASE(EMPTY_BASES)
#define STDEXEC_ATTRIBUTE_SWITCH_noinline                STDEXEC_PP_CASE(NOINLINE)
#define STDEXEC_ATTRIBUTE_SWITCH_weak                    STDEXEC_PP_CASE(WEAK)
#define STDEXEC_ATTRIBUTE_SWITCH_preferred_name          STDEXEC_PP_CASE(PREFERRED_NAME)
#define STDEXEC_ATTRIBUTE_SWITCH_musttail                STDEXEC_PP_CASE(MUSTTAIL)
#define STDEXEC_ATTRIBUTE_SWITCH___musttail__            STDEXEC_PP_CASE(MUSTTAIL)
#define STDEXEC_ATTRIBUTE_SWITCH_host                    STDEXEC_PP_CASE(HOST)
#define STDEXEC_ATTRIBUTE_SWITCH___host__                STDEXEC_PP_CASE(HOST)
#define STDEXEC_ATTRIBUTE_SWITCH_device                  STDEXEC_PP_CASE(DEVICE)
#define STDEXEC_ATTRIBUTE_SWITCH___device__              STDEXEC_PP_CASE(DEVICE)
#define STDEXEC_ATTRIBUTE_SWITCH_launch_bounds(...)      STDEXEC_PP_CASE(LAUNCH_BOUNDS(__VA_ARGS__))
#define STDEXEC_ATTRIBUTE_SWITCH___launch_bounds__(...)  STDEXEC_PP_CASE(LAUNCH_BOUNDS(__VA_ARGS__))

// By default, assume the attribute is a C++11-style attribute that can be used as-is.
#define STDEXEC_ATTRIBUTE_CASE_DEFAULT(...)              [[__VA_ARGS__]]

// [[no_unique_address]]
#if (STDEXEC_NVHPC() && STDEXEC_NVHPC_VERSION < 2305)                                              \
  || (STDEXEC_MSVC() && STDEXEC_MSVC_VERSION < 1943)                                               \
  || (STDEXEC_CLANG_CL() && STDEXEC_CLANG_VERSION < 1801)
#  define STDEXEC_ATTRIBUTE_CASE_NO_UNIQUE_ADDRESS
#elif STDEXEC_CLANG_CL() || STDEXEC_MSVC()
#  define STDEXEC_ATTRIBUTE_CASE_NO_UNIQUE_ADDRESS       [[msvc::no_unique_address]]
#else
#  define STDEXEC_ATTRIBUTE_CASE_NO_UNIQUE_ADDRESS       [[no_unique_address]]
#endif

// __attribute__((__always_inline__)) and __forceinline
#if STDEXEC_MSVC()
#  define STDEXEC_ATTRIBUTE_CASE_ALWAYS_INLINE           __forceinline
#elif STDEXEC_CLANG()
#  define STDEXEC_ATTRIBUTE_CASE_ALWAYS_INLINE           __attribute__((__always_inline__, __artificial__, __nodebug__)) inline
#elif STDEXEC_GCC()
#  define STDEXEC_ATTRIBUTE_CASE_ALWAYS_INLINE           __attribute__((__always_inline__, __artificial__)) inline
#else
#  define STDEXEC_ATTRIBUTE_CASE_ALWAYS_INLINE
#endif

// __attribute__((__weak__))
#if STDEXEC_CLANG() || STDEXEC_GCC()
#  define STDEXEC_ATTRIBUTE_CASE_WEAK                    __attribute__((__weak__))
#else
#  define STDEXEC_ATTRIBUTE_CASE_WEAK
#endif

// __declspec(empty_bases) and __declspec(noinline)
#if STDEXEC_MSVC() && !STDEXEC_CLANG_CL()
#  define STDEXEC_ATTRIBUTE_CASE_EMPTY_BASES             __declspec(empty_bases)
#  define STDEXEC_ATTRIBUTE_CASE_NOINLINE                __declspec(noinline)
#else
#  define STDEXEC_ATTRIBUTE_CASE_EMPTY_BASES
#  define STDEXEC_ATTRIBUTE_CASE_NOINLINE
#endif

// __attribute__((__preferred_name__))
#if STDEXEC_HAS_ATTRIBUTE(__preferred_name__)
#  define STDEXEC_ATTRIBUTE_CASE_PREFERRED_NAME          __attribute__((__preferred_name__))
#else
#  define STDEXEC_ATTRIBUTE_CASE_PREFERRED_NAME
#endif

// [[musttail]]
#if STDEXEC_MSVC() && !STDEXEC_CLANG_CL() && STDEXEC_MSVC_VERSION >= 1950
#  define STDEXEC_ATTRIBUTE_CASE_MUSTTAIL                [[msvc::musttail]]
#elif STDEXEC_HAS_CPP_ATTRIBUTE(clang::musttail)
#  define STDEXEC_ATTRIBUTE_CASE_MUSTTAIL                [[clang::musttail]]
#elif STDEXEC_HAS_CPP_ATTRIBUTE(gnu::musttail)
#  define STDEXEC_ATTRIBUTE_CASE_MUSTTAIL                [[gnu::musttail]]
#else
#  define STDEXEC_ATTRIBUTE_CASE_MUSTTAIL
#endif

// __host__ and __device__
#if STDEXEC_CUDA_COMPILATION() && !STDEXEC_NVHPC()
#  define STDEXEC_ATTRIBUTE_CASE_HOST                    __host__
#  define STDEXEC_ATTRIBUTE_CASE_DEVICE                  __device__
#else
#  define STDEXEC_ATTRIBUTE_CASE_HOST
#  define STDEXEC_ATTRIBUTE_CASE_DEVICE
#endif

// __launch_bounds__(...)
#if defined(__launch_bounds__) && !STDEXEC_NVHPC()
#  define STDEXEC_ATTRIBUTE_CASE_LAUNCH_BOUNDS(...)      __launch_bounds__(__VA_ARGS__)
#else
#  define STDEXEC_ATTRIBUTE_CASE_LAUNCH_BOUNDS(...)
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
// warning push/pop portability macros
#if STDEXEC_NVCC()
#  define STDEXEC_PRAGMA_PUSH()          STDEXEC_PRAGMA(nv_diagnostic push)
#  define STDEXEC_PRAGMA_POP()           STDEXEC_PRAGMA(nv_diagnostic pop)
#  define STDEXEC_PRAGMA_IGNORE_EDG(...) STDEXEC_PRAGMA(nv_diag_suppress __VA_ARGS__)
#elif STDEXEC_EDG()
#  define STDEXEC_PRAGMA_PUSH()                                                                    \
    STDEXEC_PRAGMA(diagnostic push)                                                                \
    STDEXEC_PRAGMA_IGNORE_EDG(invalid_error_number)                                                \
    STDEXEC_PRAGMA_IGNORE_EDG(invalid_error_tag)
#  define STDEXEC_PRAGMA_POP()           STDEXEC_PRAGMA(diagnostic pop)
#  define STDEXEC_PRAGMA_IGNORE_EDG(...) STDEXEC_PRAGMA(diag_suppress __VA_ARGS__)
#elif STDEXEC_CLANG() || STDEXEC_GCC()
#  define STDEXEC_PRAGMA_PUSH()                                                                    \
    STDEXEC_PRAGMA(GCC diagnostic push)                                                            \
    STDEXEC_PRAGMA_IGNORE_GNU("-Wpragmas")                                                         \
    STDEXEC_PRAGMA_IGNORE_GNU("-Wunknown-pragmas")                                                 \
    STDEXEC_PRAGMA_IGNORE_GNU("-Wunknown-warning-option")                                          \
    STDEXEC_PRAGMA_IGNORE_GNU("-Wunknown-attributes")                                              \
    STDEXEC_PRAGMA_IGNORE_GNU("-Wattributes")
#  define STDEXEC_PRAGMA_POP()           STDEXEC_PRAGMA(GCC diagnostic pop)
#  define STDEXEC_PRAGMA_IGNORE_GNU(...) STDEXEC_PRAGMA(GCC diagnostic ignored __VA_ARGS__)
#elif STDEXEC_MSVC()
#  define STDEXEC_PRAGMA_PUSH()           STDEXEC_PRAGMA(warning(push))
#  define STDEXEC_PRAGMA_POP()            STDEXEC_PRAGMA(warning(pop))
#  define STDEXEC_PRAGMA_IGNORE_MSVC(...) STDEXEC_PRAGMA(warning(disable : __VA_ARGS__))
#else
#  define STDEXEC_PRAGMA_PUSH()
#  define STDEXEC_PRAGMA_POP()
#endif

#ifndef STDEXEC_PRAGMA_IGNORE_GNU
#  define STDEXEC_PRAGMA_IGNORE_GNU(...)
#endif
#ifndef STDEXEC_PRAGMA_IGNORE_EDG
#  define STDEXEC_PRAGMA_IGNORE_EDG(...)
#endif
#ifndef STDEXEC_PRAGMA_IGNORE_MSVC
#  define STDEXEC_PRAGMA_IGNORE_MSVC(...)
#endif

#if STDEXEC_MSVC()
#  define STDEXEC_PRAGMA_OPTIMIZE_BEGIN()  STDEXEC_PRAGMA(optimize("t", on))
#  define STDEXEC_PRAGMA_OPTIMIZE_END()    STDEXEC_PRAGMA(optimize("", on))
#else
#  define STDEXEC_PRAGMA_OPTIMIZE_BEGIN()  STDEXEC_PRAGMA(GCC push_options) \
                                           STDEXEC_PRAGMA(GCC optimize("O3"))
#  define STDEXEC_PRAGMA_OPTIMIZE_END()    STDEXEC_PRAGMA(GCC pop_options)
#endif

#if !STDEXEC_MSVC() && defined(__has_builtin)
#  define STDEXEC_HAS_BUILTIN __has_builtin
#else
#  define STDEXEC_HAS_BUILTIN(...) 0
#endif

#if !STDEXEC_MSVC() && defined(__has_feature)
#  define STDEXEC_HAS_FEATURE __has_feature
#else
#  define STDEXEC_HAS_FEATURE(...) 0
#endif

#if STDEXEC_HAS_BUILTIN(__is_trivially_copyable) || STDEXEC_MSVC()
#  define STDEXEC_IS_TRIVIALLY_COPYABLE(...) __is_trivially_copyable(__VA_ARGS__)
#else
#  define STDEXEC_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable_v<__VA_ARGS__>
#endif

#if STDEXEC_HAS_BUILTIN(__is_base_of) || (STDEXEC_MSVC_VERSION >= 1914)
#  define STDEXEC_IS_BASE_OF(...) __is_base_of(__VA_ARGS__)
#else
#  define STDEXEC_IS_BASE_OF(...) std::is_base_of_v<__VA_ARGS__>
#endif

#if STDEXEC_HAS_BUILTIN(__is_convertible_to) || STDEXEC_MSVC()
#  define STDEXEC_IS_CONVERTIBLE_TO(...) __is_convertible_to(__VA_ARGS__)
#elif STDEXEC_HAS_BUILTIN(__is_convertible)
#  define STDEXEC_IS_CONVERTIBLE_TO(...) __is_convertible(__VA_ARGS__)
#else
#  define STDEXEC_IS_CONVERTIBLE_TO(...) std::is_convertible_v<__VA_ARGS__>
#endif

#if STDEXEC_HAS_BUILTIN(__is_const)
#  define STDEXEC_IS_CONST(...) __is_const(__VA_ARGS__)
#else
#  define STDEXEC_IS_CONST(...) STDEXEC::__is_const_<__VA_ARGS__>
#endif

#if STDEXEC_HAS_BUILTIN(__is_function)
#  define STDEXEC_IS_FUNCTION(...) __is_function(__VA_ARGS__)
#else
#  define STDEXEC_IS_FUNCTION(...)                                                                 \
    (!STDEXEC_IS_CONST(__VA_ARGS__) && !STDEXEC_IS_CONST(const __VA_ARGS__))
#endif

#if STDEXEC_HAS_BUILTIN(__is_same)
#  define STDEXEC_IS_SAME(...) __is_same(__VA_ARGS__)
#elif STDEXEC_HAS_BUILTIN(__is_same_as)
#  define STDEXEC_IS_SAME(...) __is_same_as(__VA_ARGS__)
#elif STDEXEC_MSVC()
// msvc replaces std::is_same_v with a compile-time constant
#  define STDEXEC_IS_SAME(...) std::is_same_v<__VA_ARGS__>
#else
#  define STDEXEC_IS_SAME(...) STDEXEC::__same_as_v<__VA_ARGS__>
#endif

#if STDEXEC_HAS_BUILTIN(__is_constructible) || STDEXEC_MSVC()
#  define STDEXEC_IS_CONSTRUCTIBLE(...) __is_constructible(__VA_ARGS__)
#else
#  define STDEXEC_IS_CONSTRUCTIBLE(...) std::is_constructible_v<__VA_ARGS__>
#endif

#if STDEXEC_HAS_BUILTIN(__is_nothrow_constructible) || STDEXEC_MSVC()
#  define STDEXEC_IS_NOTHROW_CONSTRUCTIBLE(...) __is_nothrow_constructible(__VA_ARGS__)
#else
#  define STDEXEC_IS_NOTHROW_CONSTRUCTIBLE(...) std::is_nothrow_constructible_v<__VA_ARGS__>
#endif

#if STDEXEC_HAS_BUILTIN(__is_trivially_constructible) || STDEXEC_MSVC()
#  define STDEXEC_IS_TRIVIALLY_CONSTRUCTIBLE(...) __is_trivially_constructible(__VA_ARGS__)
#else
#  define STDEXEC_IS_TRIVIALLY_CONSTRUCTIBLE(...) std::is_trivially_constructible_v<__VA_ARGS__>
#endif

#if STDEXEC_HAS_BUILTIN(__is_nothrow_assignable) || STDEXEC_MSVC()
#  define STDEXEC_IS_NOTHROW_ASSIGNABLE(...) __is_nothrow_assignable(__VA_ARGS__)
#else
#  define STDEXEC_IS_NOTHROW_ASSIGNABLE(...) std::is_nothrow_assignable_v<__VA_ARGS__>
#endif

#if STDEXEC_HAS_BUILTIN(__is_assignable) || STDEXEC_MSVC()
#  define STDEXEC_IS_ASSIGNABLE(...) __is_assignable(__VA_ARGS__)
#else
#  define STDEXEC_IS_ASSIGNABLE(...) std::is_assignable_v<__VA_ARGS__>
#endif

#if STDEXEC_HAS_BUILTIN(__is_empty) || STDEXEC_MSVC()
#  define STDEXEC_IS_EMPTY(...) __is_empty(__VA_ARGS__)
#else
#  define STDEXEC_IS_EMPTY(...) std::is_empty_v<__VA_ARGS__>
#endif

#if STDEXEC_HAS_BUILTIN(__remove_reference)
namespace STDEXEC
{
  template <class _Ty>
  using __unref_t = __remove_reference(_Ty);
}  // namespace STDEXEC

#  define STDEXEC_REMOVE_REFERENCE(...) STDEXEC::__unref_t<__VA_ARGS__>
#elif STDEXEC_HAS_BUILTIN(__remove_reference_t)
namespace STDEXEC
{
  template <class _Ty>
  using __unref_t = __remove_reference_t(_Ty);
}  // namespace STDEXEC

#  define STDEXEC_REMOVE_REFERENCE(...) STDEXEC::__unref_t<__VA_ARGS__>
#else
#  define STDEXEC_REMOVE_REFERENCE(...) ::std::remove_reference_t<__VA_ARGS__>
#endif

namespace STDEXEC
{
  template <class _Ap, class _Bp>
  inline constexpr bool __same_as_v = false;

  template <class _Ap>
  inline constexpr bool __same_as_v<_Ap, _Ap> = true;
}  // namespace STDEXEC

#if defined(__cpp_lib_unreachable) && __cpp_lib_unreachable >= 202202L
#  define STDEXEC_UNREACHABLE() std::unreachable()
#elif STDEXEC_HAS_BUILTIN(__builtin_unreachable)
#  define STDEXEC_UNREACHABLE() __builtin_unreachable()
#elif STDEXEC_MSVC()
#  define STDEXEC_UNREACHABLE(...) __assume(false)
#else
#  define STDEXEC_UNREACHABLE(...) std::terminate()
#endif

// gcc struggles with copy-elision of immovable types
#if STDEXEC_GCC()
#  define STDEXEC_IMMOVABLE(_XP) _XP(_XP&&)
#else
#  define STDEXEC_IMMOVABLE(_XP) _XP(_XP&&) = delete
#endif

#if STDEXEC_GCC()
// BUG (gcc#98995): copy elision fails when initializing a [[no_unique_address]] field
// from a function returning an object of class type by value.
// See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98995
#  define STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
#elif STDEXEC_CLANG() && (__clang_major__ >= 15 && __clang_major__ < 19)
// See https://github.com/llvm/llvm-project/issues/93563
#  define STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
#else
#  define STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS STDEXEC_ATTRIBUTE(no_unique_address)
#endif

#if STDEXEC_NVHPC()
#  include <nv/target>
#  define STDEXEC_TERMINATE() NV_IF_TARGET(NV_IS_HOST, (std::terminate();), (__trap();)) void()
#elif STDEXEC_CLANG() && defined(__CUDA__) && defined(__CUDA_ARCH__)
#  define STDEXEC_TERMINATE() (__trap(), __builtin_unreachable())
#else
#  define STDEXEC_TERMINATE() std::terminate()
#endif

// Some compilers turn on pack indexing in pre-C++26 code. We want to use it if it is
// available. Pack indexing is disabled for clang < 20 because of:
// https://github.com/llvm/llvm-project/issues/116105
#if defined(__cpp_pack_indexing) && !STDEXEC_NVCC()                                                \
  && !(STDEXEC_CLANG() && STDEXEC_CLANG_VERSION < 2000)
#  define STDEXEC_NO_STDCPP_PACK_INDEXING() 0
#else  // ^^^ has pack indexing ^^^ / vvv no pack indexing vvv
#  define STDEXEC_NO_STDCPP_PACK_INDEXING() 1
#endif  // no pack indexing

#if STDEXEC_HAS_FEATURE(thread_sanitizer) || defined(__SANITIZE_THREAD__)
#  define STDEXEC_TSAN() 1
#else
#  define STDEXEC_TSAN() 0
#endif

#if __has_include(<memory_resource>) && \
  (defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L)
#  define STDEXEC_NO_STDCPP_MEMORY_RESOURCE() 0
#else
#  define STDEXEC_NO_STDCPP_MEMORY_RESOURCE() 1
#endif

#if defined(__cpp_lib_execution) && __cpp_lib_execution >= 201603L
#  define STDEXEC_NO_STDCPP_EXECUTION_POLICY() 0
#else
#  define STDEXEC_NO_STDCPP_EXECUTION_POLICY() 1
#endif

#if defined(__cpp_lib_execution) && __cpp_lib_execution >= 201902L
#  define STDEXEC_NO_STDCPP_UNSEQUENCED_EXECUTION_POLICY() 0
#else
#  define STDEXEC_NO_STDCPP_UNSEQUENCED_EXECUTION_POLICY() 1
#endif

#if defined(__cpp_lib_parallel_algorithm) && __cpp_lib_parallel_algorithm >= 201603L
#  define STDEXEC_NO_STDCPP_PARALLEL_ALGORITHMS() 0
#else
#  define STDEXEC_NO_STDCPP_PARALLEL_ALGORITHMS() 1
#endif

#if defined(__cpp_explicit_this_parameter) && (__cpp_explicit_this_parameter >= 202110L)
#  define STDEXEC_NO_STDCPP_EXPLICIT_THIS_PARAMETER() 0
#else
#  define STDEXEC_NO_STDCPP_EXPLICIT_THIS_PARAMETER() 1
#endif

#if defined(__cpp_rtti) && __cpp_rtti >= 199711L
#  define STDEXEC_NO_STDCPP_RTTI() 0
#else
#  define STDEXEC_NO_STDCPP_RTTI() 1
#endif

// MSVC always has typeid support, even when RTTI is disabled
#if STDEXEC_NO_STDCPP_RTTI() && !STDEXEC_MSVC()
#  define STDEXEC_NO_STDCPP_TYPEID() 1
#else
#  define STDEXEC_NO_STDCPP_TYPEID() 0
#endif

// Perhaps the stdlib lacks support for concepts
#if __has_include(<concepts>) && __cpp_lib_concepts >= 202002L
#  define STDEXEC_NO_STDCPP_CONCEPTS_HEADER() 0
#else
#  define STDEXEC_NO_STDCPP_CONCEPTS_HEADER() 1
#endif

#if defined(__cpp_if_consteval) && __cpp_if_consteval >= 202106L
#  define STDEXEC_IF_CONSTEVAL     if consteval
#  define STDEXEC_IF_NOT_CONSTEVAL if !consteval
#elif STDEXEC_GCC()
#  define STDEXEC_IF_CONSTEVAL                                                                     \
    STDEXEC_PRAGMA_PUSH()                                                                          \
    STDEXEC_PRAGMA_IGNORE_GNU("-Wtautological-compare")                                            \
    if (std::is_constant_evaluated())                                                              \
    STDEXEC_PRAGMA_POP()
#  define STDEXEC_IF_NOT_CONSTEVAL STDEXEC_IF_CONSTEVAL {} else
#else
#  define STDEXEC_IF_CONSTEVAL     if (std::is_constant_evaluated())
#  define STDEXEC_IF_NOT_CONSTEVAL STDEXEC_IF_CONSTEVAL {} else
#endif

#if defined(STDEXEC_ASSERT)
// nothing to do, user has provided their own assertion macro
#elif defined(STDEXEC_ASSERT_FN)
// legacy way to customize assertions, still supported for backward compatibility
#  define STDEXEC_ASSERT(_XP) STDEXEC_ASSERT_FN(_XP)
#else
#  define STDEXEC_ASSERT(_XP)                                                                      \
  do                                                                                               \
  {                                                                                                \
    STDEXEC_IF_CONSTEVAL                                                                           \
    {                                                                                              \
      if (!(_XP))                                                                                  \
        STDEXEC::__throw_assertion_failure();                                                      \
    }                                                                                              \
    else                                                                                           \
    {                                                                                              \
      assert(_XP);                                                                                 \
    }                                                                                              \
  } while (false)
#endif

namespace STDEXEC
{
  struct __assertion_failure
  {};

  inline void __throw_assertion_failure()
  {
    throw __assertion_failure{};
  }
}  // namespace STDEXEC

#define STDEXEC_AUTO_RETURN(...)                                                                   \
  noexcept(noexcept(__VA_ARGS__))->decltype(__VA_ARGS__) {                                         \
    return __VA_ARGS__;                                                                            \
  }

#if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING == 0
#  undef STDEXEC_ENABLE_EXTRA_TYPE_CHECKING
#  define STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() 0
#else
#  undef STDEXEC_ENABLE_EXTRA_TYPE_CHECKING
#  define STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() 1
#endif

#if STDEXEC_CUDA_COMPILATION() && defined(__CUDA_ARCH__)
#  define STDEXEC_NO_STDCPP_EXCEPTIONS() 1
#elif STDEXEC_MSVC() || STDEXEC_CLANG_CL()
#  define STDEXEC_NO_STDCPP_EXCEPTIONS() (_HAS_EXCEPTIONS == 0) || (_CPPUNWIND == 0)
#else
#  define STDEXEC_NO_STDCPP_EXCEPTIONS() (__EXCEPTIONS == 0)
#endif

#if defined(__cpp_constexpr_exceptions) && __cpp_constexpr_exceptions >= 202411L
#  if !STDEXEC_NO_STDCPP_EXCEPTIONS()
// https://wg21.link/p3068
#    define STDEXEC_NO_STDCPP_CONSTEXPR_EXCEPTIONS() 0
#  else
#    define STDEXEC_NO_STDCPP_CONSTEXPR_EXCEPTIONS() 1
#  endif
#else
#  define STDEXEC_NO_STDCPP_CONSTEXPR_EXCEPTIONS() 1
#endif

// We need to treat host and device separately
#if STDEXEC_CUDA_COMPILATION() && defined(__CUDA_ARCH__) && !STDEXEC_NVHPC()
#  define STDEXEC_GLOBAL_CONSTANT STDEXEC_ATTRIBUTE(device) constexpr
#else
#  define STDEXEC_GLOBAL_CONSTANT inline constexpr
#endif

#if STDEXEC_CUDA_COMPILATION() || __has_include(<cuda_runtime_api.h>)
#  define STDEXEC_HAS_CTK() 1
#else
#  define STDEXEC_HAS_CTK() 0
#endif

// clang-format off
#if STDEXEC_HAS_CTK() && __has_include(<nv/target>)
#  include <nv/target>
#  define STDEXEC_IF_HOST(...)     NV_IF_TARGET(NV_IS_HOST, (__VA_ARGS__;))
#  define STDEXEC_IF_DEVICE(...)   NV_IF_TARGET(NV_IS_DEVICE, (__VA_ARGS__;))
#else
#  define STDEXEC_IF_HOST(...)     {__VA_ARGS__;}
#  define STDEXEC_IF_DEVICE(...)
#endif
// clang-format on

// CUDA compilers preinclude cuda_runtime.h, but if we're not compiling for CUDA then we
// need to include it ourselves.
#if STDEXEC_HAS_CTK() && !STDEXEC_CUDA_COMPILATION()
#  include <cuda_runtime_api.h>
#endif

// clang-format off

// The following macros are used to conditionally compile exception handling code. They
// are used in the same way as `try` and `catch`, but they allow for different behavior
// based on whether exceptions are enabled or not, and whether the code is being compiled
// for device or not.
//
// Usage:
//   STDEXEC_TRY
//   {
//     can_throw();               // Code that may throw an exception
//   }
//   STDEXEC_CATCH (cuda_error& e)  // Handle CUDA exceptions
//   {
//     printf("CUDA error: %s\n", e.what());
//   }
//   STDEXEC_CATCH_ALL              // Handle any other exceptions
//   {
//     printf("unknown error\n");
//   }
#if STDEXEC_NO_STDCPP_EXCEPTIONS()
#  define STDEXEC_TRY               if constexpr (true) {
#  define STDEXEC_CATCH(...)        } else if constexpr (__VA_ARGS__ = ::STDEXEC::__catch_any_lvalue; false) {
#  define STDEXEC_CATCH_ALL         } else if constexpr (true) {} else
#  define STDEXEC_THROW(...)        ::STDEXEC::__terminate()
#  define STDEXEC_CATCH_FALLTHROUGH } else {}
#else
#  define STDEXEC_TRY               try
#  define STDEXEC_CATCH             catch
#  define STDEXEC_CATCH_ALL         catch(...)
#  define STDEXEC_THROW(...)        throw __VA_ARGS__
#  define STDEXEC_CATCH_FALLTHROUGH
#endif

// clang-format on

namespace STDEXEC
{
  // Used by the STDEXEC_CATCH macro to provide a stub initialization of the exception object.
  inline constexpr struct __catch_any_lvalue_t
  {
    template <class _Tp>
    STDEXEC_ATTRIBUTE(host, device)
    constexpr operator _Tp&() const noexcept;
  } __catch_any_lvalue{};

  STDEXEC_ATTRIBUTE(noreturn, host, device)
  inline void __terminate() noexcept
  {
    STDEXEC_IF_HOST(::exit(-1))
    STDEXEC_IF_DEVICE(__trap())
    STDEXEC_UNREACHABLE();
  }
}  // namespace STDEXEC

///////////////////////////////////////////////////////////////////////////////
/// To hook a customization point like STDEXEC::connect, define a member
/// function like this:
///
/// @code
/// template <__std::same_as<self_t> Self, class Receiver>
/// STDEXEC_EXPLICIT_THIS_BEGIN(auto connect)(this Self&& self, Receiver rcvr) {
///   return ...;
/// }
/// STDEXEC_EXPLICIT_THIS_END(connect)
/// @endcode

#if !STDEXEC_NO_STDCPP_EXPLICIT_THIS_PARAMETER()

#  define STDEXEC_EXPLICIT_THIS_BEGIN(...) __VA_ARGS__
#  define STDEXEC_EXPLICIT_THIS_END(...)

#else

#  define STDEXEC_EXPLICIT_THIS_CALL_OPERATOR_PROBE_operator() STDEXEC_PP_PROBE(~, 1)

#  define STDEXEC_EXPLICIT_THIS_CALL_OPERATOR_PROBE(_NAME)                                      \
    STDEXEC_PP_CHECK(STDEXEC_PP_CAT(STDEXEC_EXPLICIT_THIS_CALL_OPERATOR_PROBE_, _NAME))

#  define STDEXEC_EXPLICIT_THIS_MANGLE(_NAME)                                                   \
    STDEXEC_PP_CAT(__static_,                                                                   \
                   STDEXEC_PP_IIF(STDEXEC_EXPLICIT_THIS_CALL_OPERATOR_PROBE(_NAME),             \
                                  _call,                                                        \
                                  _NAME))

#  define STDEXEC_EXPLICIT_THIS_EAT_this
#  define STDEXEC_EXPLICIT_THIS_MANGLE_auto auto STDEXEC_EXPLICIT_THIS_MANGLE STDEXEC_PP_LPAREN()
#  define STDEXEC_EXPLICIT_THIS_MANGLE_void void STDEXEC_EXPLICIT_THIS_MANGLE STDEXEC_PP_LPAREN()
#  define STDEXEC_EXPLICIT_THIS_MANGLE_bool bool STDEXEC_EXPLICIT_THIS_MANGLE STDEXEC_PP_LPAREN()

#  define STDEXEC_EXPLICIT_THIS_ARGS(...)                                                       \
    STDEXEC_PP_CAT(STDEXEC_EXPLICIT_THIS_EAT_, __VA_ARGS__) STDEXEC_PP_RPAREN()

#  define STDEXEC_EXPLICIT_THIS_BEGIN(...)                                                      \
    static STDEXEC_PP_EXPAND(STDEXEC_PP_CAT(STDEXEC_EXPLICIT_THIS_MANGLE_, __VA_ARGS__)         \
                             STDEXEC_PP_RPAREN()) STDEXEC_PP_LPAREN() STDEXEC_EXPLICIT_THIS_ARGS

#  define STDEXEC_EXPLICIT_THIS_END(_NAME)                                                      \
    template <class... _Ts>                                                                     \
    STDEXEC_ATTRIBUTE(always_inline, host, device)                                              \
    auto _NAME(_Ts&&... __args) && STDEXEC_AUTO_RETURN                                          \
    (                                                                                           \
      STDEXEC_EXPLICIT_THIS_MANGLE(_NAME)(std::move(*this), static_cast<_Ts&&>(__args)...)      \
    )                                                                                           \
                                                                                                \
    template <class... _Ts>                                                                     \
    STDEXEC_ATTRIBUTE(always_inline, host, device)                                              \
    auto _NAME(_Ts&&... __args) & STDEXEC_AUTO_RETURN                                           \
    (                                                                                           \
      STDEXEC_EXPLICIT_THIS_MANGLE(_NAME)(*this, static_cast<_Ts&&>(__args)...)                 \
    )                                                                                           \
                                                                                                \
    template <class... _Ts>                                                                     \
    STDEXEC_ATTRIBUTE(always_inline, host, device)                                              \
    auto _NAME(_Ts&&... __args) const & STDEXEC_AUTO_RETURN                                     \
    (                                                                                           \
      STDEXEC_EXPLICIT_THIS_MANGLE(_NAME)(*this, static_cast<_Ts&&>(__args)...)                 \
    )

#endif  // STDEXEC_NO_STDCPP_EXPLICIT_THIS_PARAMETER()

////////////////////////////////////////////////////////////////////////////////////////////////////
#if STDEXEC_CLANG() && STDEXEC_CUDA_COMPILATION() && !defined(STDEXEC_CLANG_TIDY_INVOKED)
#  define STDEXEC_HAS_HOST_DEVICE_OVERLOADS() 1
#else
#  define STDEXEC_HAS_HOST_DEVICE_OVERLOADS() 0
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
#if STDEXEC_MSVC()
#  if _MSC_VER >= 1935
#    define STDEXEC_PRETTY_FUNCTION() __builtin_FUNCSIG()
#  else
#    define STDEXEC_PRETTY_FUNCTION() __FUNCSIG__
#  endif
#else
#  define STDEXEC_PRETTY_FUNCTION() __PRETTY_FUNCTION__
#endif

#if __cplusplus >= 202211L
#  define STDEXEC_CONSTEXPR_CXX23        constexpr
#  define STDEXEC_CONSTEXPR_LOCAL static constexpr
#else
#  define STDEXEC_CONSTEXPR_CXX23
#  define STDEXEC_CONSTEXPR_LOCAL constexpr
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
#if !defined(STDEXEC_DEMANGLE_SENDER_NAMES) && STDEXEC_MSVC()
#  define STDEXEC_DEMANGLE_SENDER_NAMES
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
// clang-tidy struggles with the CUDA function annotations
#if STDEXEC_CLANG() && STDEXEC_CUDA_COMPILATION() && defined(STDEXEC_CLANG_TIDY_INVOKED)
#  include <cuda_runtime_api.h>  // IWYU pragma: keep
#  if !defined(__launch_bounds__)
#    define __launch_bounds__(...)
#  endif

#  if !defined(__host__)
#    define __host__
#  endif

#  if !defined(__device__)
#    define __device__
#  endif

#  if !defined(__global__)
#    define __global__
#  endif
#endif

namespace STDEXEC
{}
