Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace is_detected (type) by is_supported_operation (variable). #13320

Merged
merged 4 commits into from
Feb 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
112 changes: 88 additions & 24 deletions include/deal.II/base/template_constraints.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,49 @@

DEAL_II_NAMESPACE_OPEN

// Detection idiom from Version 2 of the C++ Extensions for Library
// Detection idiom adapted from Version 2 of the C++ Extensions for Library
// Fundamentals, ISO/IEC TS 19568:2017
namespace internal
{
/**
* A namespace used to declare the machinery for detecting whether a specific
* class supports an operation. This approach simulates C++20-style
* concepts with language standards before C++20.
*/
namespace SupportsOperation
{
template <class...>
using void_t = void;

// primary template handles all types not supporting the archetypal Op
/**
* The primary template class used in detecting operations. If the
* compiler does not choose the specialization, then the fall-back
* case is this general template, which then declares member variables
* and types according to the failed detection.
*/
template <class Default,
class /*AlwaysVoid*/,
class AlwaysVoid,
template <class...>
class Op,
class... /*Args*/>
class... Args>
struct detector
{
using value_t = std::false_type;
using type = Default;
};

// specialization recognizes and handles only types supporting Op
/**
* A specialization of the general template.
*
* The trick this class uses is that, just like the general template,
* its second argument is always `void`, but here it is written as
* `void_t<Op<Args...>>` and consequently the compiler will only select this
* specialization if `Op<Args...>` is in fact a valid type. This means that
* the operation we seek to understand is indeed supported.
*
* This specialization then declares member variables and types according
* to the successful detection.
*/
template <class Default, template <class...> class Op, class... Args>
struct detector<Default, void_t<Op<Args...>>, Op, Args...>
{
Expand All @@ -57,10 +78,17 @@ namespace internal
};


// base class for nonesuch to inherit from so it is not an aggregate
/**
* A base class for the `nonesuch` type to inherit from so it is not an
* aggregate.
*/
struct nonesuch_base
{};

/**
* A type that can not be used in any reasonable way and consequently
* can be used to indicate a failed detection in template metaprogramming.
*/
struct nonesuch : private nonesuch_base
{
~nonesuch() = delete;
Expand All @@ -69,29 +97,64 @@ namespace internal
operator=(nonesuch const &) = delete;
};

} // namespace SupportsOperation
template <class Default, template <class...> class Op, class... Args>
using detected_or = detector<Default, void, Op, Args...>;

template <class Default, template <class...> class Op, class... Args>
using detected_or =
internal::SupportsOperation::detector<Default, void, Op, Args...>;
template <template <class...> class Op, class... Args>
using is_detected = typename detected_or<nonesuch, Op, Args...>::value_t;

template <template <class...> class Op, class... Args>
using is_detected =
typename detected_or<SupportsOperation::nonesuch, Op, Args...>::value_t;
template <template <class...> class Op, class... Args>
using detected_t = typename detected_or<nonesuch, Op, Args...>::type;

template <template <class...> class Op, class... Args>
using detected_t =
typename detected_or<SupportsOperation::nonesuch, Op, Args...>::type;
template <class Default, template <class...> class Op, class... Args>
using detected_or_t = typename detected_or<Default, Op, Args...>::type;

template <class Default, template <class...> class Op, class... Args>
using detected_or_t = typename detected_or<Default, Op, Args...>::type;
template <class Expected, template <class...> class Op, class... Args>
using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;

template <class To, template <class...> class Op, class... Args>
using is_detected_convertible =
std::is_convertible<detected_t<Op, Args...>, To>;
} // namespace SupportsOperation

template <class Expected, template <class...> class Op, class... Args>
using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;

template <class To, template <class...> class Op, class... Args>
using is_detected_convertible =
std::is_convertible<detected_t<Op, Args...>, To>;
/**
* A `constexpr` variable that describes whether or not `Op<Args...>` is a
* valid expression.
*
* The way this is used is to define an `Op` operation template that
* describes the operation we want to perform, and `Args` is a template
* pack that describes the arguments to the operation. This variable
* then states whether the operation, with these arguments, leads to
* a valid C++ expression.
*
* An example is if one wanted to find out whether a type `T` has
* a `get_mpi_communicator()` member function. In that case, one would write
* the operation as
* @code
* template <typename T>
* using get_mpi_communicator_op
* = decltype(std::declval<T>().get_mpi_communicator());
* @endcode
* and could define a variable like
* @code
* template <typename T>
* constexpr bool has_get_mpi_communicator =
* is_supported_operation<get_mpi_communicator_op, T>;
* @endcode
*
* The trick used here is that `get_mpi_communicator_op` is a general
* template, but when used with a type that does *not* have a
* `get_mpi_communicator()` member variable, the `decltype(...)` operation
* will fail because its argument does not represent a valid expression for
* such a type. In other words, for such types `T` that do not have such a
* member function, the general template `get_mpi_communicator_op` represents
* a valid declaration, but the instantiation `get_mpi_communicator_op<T>`
* is not, and the variable declared here detects and reports this.
*/
template <template <class...> class Op, class... Args>
constexpr bool is_supported_operation =
SupportsOperation::is_detected<Op, Args...>::value;
} // namespace internal


Expand Down Expand Up @@ -171,7 +234,8 @@ using begin_and_end_t =
decltype(std::begin(std::declval<T>()), std::end(std::declval<T>()));

template <typename T>
using has_begin_and_end = internal::is_detected<begin_and_end_t, T>;
constexpr bool has_begin_and_end =
internal::is_supported_operation<begin_and_end_t, T>;



Expand Down
4 changes: 2 additions & 2 deletions include/deal.II/base/work_stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -1213,7 +1213,7 @@ namespace WorkStream
typename ScratchData,
typename CopyData,
typename = typename std::enable_if<
has_begin_and_end<IteratorRangeType>::value>::type>
has_begin_and_end<IteratorRangeType>>::type>
void
run(IteratorRangeType iterator_range,
Worker worker,
Expand Down Expand Up @@ -1437,7 +1437,7 @@ namespace WorkStream
typename ScratchData,
typename CopyData,
typename = typename std::enable_if<
has_begin_and_end<IteratorRangeType>::value>::type>
has_begin_and_end<IteratorRangeType>>::type>
void
run(IteratorRangeType iterator_range,
MainClass & main_object,
Expand Down
48 changes: 24 additions & 24 deletions include/deal.II/lac/la_parallel_vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -1891,7 +1891,8 @@ namespace internal
decltype(std::declval<T>().get_mpi_communicator());

template <typename T>
using has_get_mpi_communicator = is_detected<get_mpi_communicator_t, T>;
static constexpr bool has_get_mpi_communicator =
is_supported_operation<get_mpi_communicator_t, T>;

// A helper type-trait that leverage SFINAE to figure out if type T has
// void T::locally_owned_domain_indices()
Expand All @@ -1900,8 +1901,8 @@ namespace internal
decltype(std::declval<T>().locally_owned_domain_indices());

template <typename T>
using has_locally_owned_domain_indices =
is_detected<locally_owned_domain_indices_t, T>;
static constexpr bool has_locally_owned_domain_indices =
is_supported_operation<locally_owned_domain_indices_t, T>;

// A helper type-trait that leverage SFINAE to figure out if type T has
// void T::locally_owned_range_indices()
Expand All @@ -1910,8 +1911,8 @@ namespace internal
decltype(std::declval<T>().locally_owned_range_indices());

template <typename T>
using has_locally_owned_range_indices =
is_detected<locally_owned_range_indices_t, T>;
static constexpr bool has_locally_owned_range_indices =
is_supported_operation<locally_owned_range_indices_t, T>;

// A helper type-trait that leverage SFINAE to figure out if type T has
// void T::initialize_dof_vector(VectorType v)
Expand All @@ -1920,14 +1921,15 @@ namespace internal
decltype(std::declval<T>().initialize_dof_vector());

template <typename T>
using has_initialize_dof_vector = is_detected<initialize_dof_vector_t, T>;
static constexpr bool has_initialize_dof_vector =
is_supported_operation<initialize_dof_vector_t, T>;

// Used for (Trilinos/PETSc)Wrappers::SparseMatrix
template <typename MatrixType,
typename std::enable_if<
has_get_mpi_communicator<MatrixType>::value &&
has_locally_owned_domain_indices<MatrixType>::value,
MatrixType>::type * = nullptr>
template <
typename MatrixType,
typename std::enable_if<has_get_mpi_communicator<MatrixType> &&
has_locally_owned_domain_indices<MatrixType>,
MatrixType>::type * = nullptr>
static void
reinit_domain_vector(MatrixType & mat,
LinearAlgebra::distributed::Vector<Number> &vec,
Expand All @@ -1938,10 +1940,9 @@ namespace internal
}

// Used for MatrixFree and DiagonalMatrix
template <
typename MatrixType,
typename std::enable_if<has_initialize_dof_vector<MatrixType>::value,
MatrixType>::type * = nullptr>
template <typename MatrixType,
typename std::enable_if<has_initialize_dof_vector<MatrixType>,
MatrixType>::type * = nullptr>
static void
reinit_domain_vector(MatrixType & mat,
LinearAlgebra::distributed::Vector<Number> &vec,
Expand All @@ -1953,11 +1954,11 @@ namespace internal
}

// Used for (Trilinos/PETSc)Wrappers::SparseMatrix
template <typename MatrixType,
typename std::enable_if<
has_get_mpi_communicator<MatrixType>::value &&
has_locally_owned_range_indices<MatrixType>::value,
MatrixType>::type * = nullptr>
template <
typename MatrixType,
typename std::enable_if<has_get_mpi_communicator<MatrixType> &&
has_locally_owned_range_indices<MatrixType>,
MatrixType>::type * = nullptr>
static void
reinit_range_vector(MatrixType & mat,
LinearAlgebra::distributed::Vector<Number> &vec,
Expand All @@ -1968,10 +1969,9 @@ namespace internal
}

// Used for MatrixFree and DiagonalMatrix
template <
typename MatrixType,
typename std::enable_if<has_initialize_dof_vector<MatrixType>::value,
MatrixType>::type * = nullptr>
template <typename MatrixType,
typename std::enable_if<has_initialize_dof_vector<MatrixType>,
MatrixType>::type * = nullptr>
static void
reinit_range_vector(MatrixType & mat,
LinearAlgebra::distributed::Vector<Number> &vec,
Expand Down