include/cuda/experimental/__stf/utility/traits.cuh
File members: include/cuda/experimental/__stf/utility/traits.cuh
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#pragma once
#include <cuda/__cccl_config>
#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
# pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
# pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
# pragma system_header
#endif // no system header
#include <cuda/std/mdspan>
#include <cuda/experimental/__stf/utility/core.cuh>
#include <array>
#include <cassert>
#include <string_view>
#include <tuple>
namespace cuda::experimental::stf
{
namespace reserved
{
// We use this function as a detector for what __PRETTY_FUNCTION__ looks like
template <typename T>
constexpr ::std::string_view type_name_IMPL()
{
#if defined(_CCCL_COMPILER_MSVC)
return __FUNCSIG__;
#else // ^^^ _CCCL_COMPILER_MSVC ^^^ / vvv !_CCCL_COMPILER_MSVC vvv
return __PRETTY_FUNCTION__;
#endif // !_CCCL_COMPILER_MSVC
}
// Length of prefix and suffix in __PRETTY_FUNCTION__ when used with `type_name`.
inline constexpr ::std::pair<size_t, size_t> type_name_affixes = [] {
const auto p = type_name_IMPL<double>();
const auto target = ::std::string_view("double");
const auto len = target.size();
// Simulate p.find() by hand because clang can't do it.
size_t i = target.npos;
for (std::size_t start = 0; start <= p.size() - len; ++start)
{
if (p.substr(start, len) == target)
{
i = start; // Found the substring, set i to the starting position
break; // Exit loop after finding the first match
}
}
auto j = p.size() - i - len;
return ::std::pair{i, j};
}();
template <class T>
constexpr ::std::string_view type_name_impl()
{
#if defined(_CCCL_COMPILER_MSVC)
constexpr ::std::string_view p = __FUNCSIG__;
// MSVC does not provide constexpr methods so we make this utility much simpler and return __FUNCSIG__ directly
return p;
#else // ^^^ _CCCL_COMPILER_MSVC ^^^ / vvv !_CCCL_COMPILER_MSVC vvv
::std::string_view p = __PRETTY_FUNCTION__;
return p.substr(type_name_affixes.first, p.size() - type_name_affixes.first - type_name_affixes.second);
#endif // !_CCCL_COMPILER_MSVC
}
} // namespace reserved
template <class T>
inline constexpr ::std::string_view type_name = reserved::type_name_impl<T>();
template <typename Tuple, typename Fun>
constexpr auto tuple2tuple(const Tuple& t, Fun&& f)
{
return ::std::apply(
[&](auto&&... x) {
return ::std::tuple(f(::std::forward<decltype(x)>(x))...);
},
t);
}
/*
* @brief A function that will fail to compile, and result in an error message
* with type T. Used internally for debugging. Since this uses a static_assert,
* it will break compilation even if the function is called in a path that is
* supposed to be unreachable !
*
* @tparam T A type which we want to display.
*/
template <typename T>
class print_type_name_and_fail
{
static_assert(::std::integral_constant<T*, nullptr>::value, "Type name is: ");
};
namespace reserved
{
template <class T>
class meyers_singleton
{
protected:
template <class U>
struct wrapper
{
using type = U;
};
friend typename wrapper<T>::type;
meyers_singleton() = default;
~meyers_singleton() = default;
meyers_singleton(const meyers_singleton&) = delete;
meyers_singleton(meyers_singleton&&) = delete;
public:
static T& instance()
{
static_assert(!::std::is_default_constructible_v<T>,
"Make the default constructor of your Meyers singleton protected.");
static_assert(!::std::is_destructible_v<T>, "Make the destructor of your Meyers singleton protected.");
static_assert(!::std::is_copy_constructible_v<T>, "Disable the copy constructor of your Meyers singleton.");
static_assert(!::std::is_move_constructible_v<T>, "Disable the move constructor of your Meyers singleton.");
struct U : T
{};
static U instance;
return instance;
}
};
} // end namespace reserved
template <typename Array>
auto to_tuple(Array&& array)
{
return tuple2tuple(::std::forward<Array>(array), [](auto&& e) {
return ::std::forward<decltype(e)>(e);
});
}
template <typename T, size_t n>
using array_tuple = decltype(to_tuple(::std::array<T, n>{}));
// Mini-unittest
static_assert(::std::is_same_v<array_tuple<size_t, 3>, ::std::tuple<size_t, size_t, size_t>>);
namespace reserved
{
template <typename T0, typename... Ts>
::cuda::std::array<T0, 1 + sizeof...(Ts)> to_cuda_array(const ::std::tuple<T0, Ts...>& obj)
{
::cuda::std::array<T0, 1 + sizeof...(Ts)> result;
each_in_tuple(obj, [&](auto index, const auto& value) {
result[index] = value;
});
return result;
}
template <typename T, size_t N>
::cuda::std::array<T, N> convert_to_cuda_array(const ::std::array<T, N>& std_array)
{
::cuda::std::array<T, N> result;
for (size_t i = 0; i < N; i++)
{
result[i] = std_array[i];
}
return result;
}
} // end namespace reserved
template <typename T, typename P0, typename... P>
T only_convertible(P0&& p0, P&&... p)
{
if constexpr (::std::is_convertible_v<P0, T>)
{
((void) p, ...);
static_assert(!(::std::is_convertible_v<P, T> || ...), "Duplicate argument type found");
return ::std::forward<P0>(p0);
}
else
{
// Ignore current head and recurse to tail
return only_convertible<T>(::std::forward<P>(p)...);
}
}
template <typename T, typename... P>
auto all_convertible(P&&... p)
{
// We use a union here to prevent the compiler from calling the destructor of the array.
// All construction/destruction will be done manually for efficiency purposes.
static constexpr size_t size = (::std::is_convertible_v<P, T> + ...);
unsigned char buffer[size * sizeof(T)];
auto& result = *reinterpret_cast<::std::array<T, size>*>(&buffer[0]);
size_t i = 0; // marks the already-constructed portion of the array
try
{
each_in_pack(
[&](auto&& e) {
if constexpr (::std::is_convertible_v<decltype(e), T>)
{
new (result.data() + i) T(::std::forward<decltype(e)>(e));
++i;
}
},
::std::forward<P>(p)...);
return mv(result);
}
catch (...)
{
for (size_t j = 0; j < i; ++j)
{
result[j].~T();
}
throw;
}
}
/*
* @brief Chooses a parameter from `P...` of a type convertible to `T`. If found, it is returned. If no such parameter
* is found, returns `default_v`.
*
* For now only value semantics are supported.
*
* @tparam T Result type
* @tparam P Variadic parameter types
* @param default_v Default value
* @param p Variadic parameter values
* @return T Either the first convertible parameter, or `default_v` if no such parameter is found
*/
template <typename T, typename... P>
T only_convertible_or([[maybe_unused]] T default_v, P&&... p)
{
if constexpr (!(::std::is_convertible_v<P, T> || ...))
{
((void) p, ...);
return default_v;
}
else
{
return only_convertible<T>(::std::forward<P>(p)...);
}
}
namespace reserved
{
/* Checks whether a collection of `DataTypes` objects can be unambiguously initialized (in some order)
from a collection of `ArgTypes` objects. Not all objects must be initialized,
e.g. `check_initialization<int, int*>(1)` passes. */
template <typename... DataTypes>
struct check_initialization
{
/* Yields the number of types in `Ts` to which `T` can be converted. */
template <typename T>
static constexpr int count_convertibilty = (::std::is_convertible_v<T, DataTypes> + ... + 0);
template <typename... ArgTypes>
static constexpr void from()
{
(
[] {
using T = ArgTypes;
static_assert(count_convertibilty<T> > 0,
"Incompatible argument: argument type doesn't match any member type.");
static_assert(count_convertibilty<T> == 1,
"Ambiguous argument: argument type converts to more than one member type.");
}(),
...); // This expands ArgTypes
}
};
} // namespace reserved
template <typename... ArgTypes, typename... DataTypes>
void shuffled_args_check(const DataTypes&...)
{
reserved::check_initialization<DataTypes...>::template from<ArgTypes...>();
}
template <typename... DataTypes, typename... ArgTypes>
::std::tuple<DataTypes...> shuffled_tuple(ArgTypes... args)
{
reserved::check_initialization<DataTypes...>::template from<ArgTypes...>();
return ::std::tuple<DataTypes...>{only_convertible_or(DataTypes(), mv(args)...)...};
}
template <typename... DataTypes, typename... ArgTypes>
auto shuffled_array_tuple(ArgTypes... args)
{
reserved::check_initialization<DataTypes...>::template from<ArgTypes...>();
return ::std::tuple{all_convertible<DataTypes>(mv(args)...)...};
}
namespace reserved
{
template <typename T, typename = void>
struct has_ostream_operator : ::std::false_type
{};
template <typename T>
struct has_ostream_operator<T, decltype(void(::std::declval<::std::ostream&>() << ::std::declval<const T&>()), void())>
: ::std::true_type
{};
} // end namespace reserved
} // namespace cuda::experimental::stf