Skip to content
Snippets Groups Projects
Commit 4c4699e2 authored by Carsten Gräser's avatar Carsten Gräser
Browse files

[concept] Add concept check utilities

The main feature of this header is the constexpr function
models(). The result of

  models<C, T1, ..., TN>()

indicated if the concept C is modeled by the collection
(T1, ..., TN) of types. A concept is a list of valid
expressions that a type must support. Concepts can refine
other concepts to inherit all requirements from those
base concepts.

Besides the concept check function itself, the header contains
some helper functions of the form require requireFoo for additional
checks. The purpose of those functions is to transform a non-satisfied
condition Foo to a substitution failure, such that the corresponding
expression is not valid. E.g.,

  requireConvertible<R>(t.f())

is not valid unless the result of t.f() is convertible to R.
parent 22c410ca
No related branches found
No related tags found
1 merge request!9Feature/conceptcheck
// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// vi: set et ts=4 sw=2 sts=2:
#ifndef DUNE_COMMON_CONCEPT_HH
#define DUNE_COMMON_CONCEPT_HH
#include <type_traits>
#include <utility>
#include <tuple>
#include <dune/common/prioritytag.hh>
#include <dune/common/typelist.hh>
namespace Dune {
namespace Concept {
/**
* \brief Base class for refined concepts.
*
* If a new concept should refine one or more existing concepts,
* this can be achieved by deriving the new concept from
* Refines<C1,...,CN> where C1, ..., CN are the concepts
* to be refined. If you want to refine several concepts
* they should all be put in a single Refines<...> base
* class.
*
* \tparam BaseConcepts The list of concepts to be refined.
*/
template<class... BaseConcepts>
struct Refines
{
typedef TypeList<BaseConcepts...> BaseConceptList;
};
// Forward declaration
template<class C, class... T>
constexpr bool models();
// #############################################################################
// # All functions following here are implementation details
// # for the models() function below.
// #############################################################################
namespace Imp
{
// Here is the implementation of the concept checking.
// The first two overloads do the magic for checking
// if the requirements of a concept are satisfied.
// The rest is just for checking base concepts in case
// of refinement.
// This overload is present if type substitution for
// C::require(T...) is successful, i.e., if the T...
// matches the requirement of C. In this case this
// overload is selected because PriorityTag<1>
// is a better match for PrioriryTag<42> than
// PriorityTag<0> in the default overload.
template<class C, class... T,
decltype(std::declval<C>().require(std::declval<T>()...), 0) =0>
constexpr std::true_type matchesRequirement(PriorityTag<1>)
{ return {}; }
// If the above overload is ruled out by SFINAE because
// the T... does not match the requirements of C, then
// this default overload drops in.
template<class C, class... T>
constexpr std::false_type matchesRequirement(PriorityTag<0>)
{ return {}; }
// An empty list C of concepts is always matched by T...
template<class...T>
constexpr bool modelsConceptList(TypeList<>)
{ return true; }
// A nonempty list C0,..,CN of concepts is modeled
// by T... if it models the concept C0
// and all concepts in the list C1,..,CN.
template<class...T, class C0, class... CC>
constexpr bool modelsConceptList(TypeList<C0, CC...>)
{ return models<C0, T...>() and modelsConceptList<T...>(TypeList<CC...>()); }
// If C is an unrefined concept, then T... models C
// if it matches the requirement of C.
template<class C, class... T>
constexpr bool modelsConcept(PriorityTag<0>)
{ return matchesRequirement<C, T...>(PriorityTag<42>()); }
// If C is a refined concept, then T... models C
// if it matches the requirement of C and of
// all base concepts.
//
// This overload is used if C::BaseConceptList exists
// due to its higher priority.
template<class C, class... T,
decltype(typename C::BaseConceptList(), 0) = 0>
constexpr bool modelsConcept(PriorityTag<1>)
{ return matchesRequirement<C, T...>(PriorityTag<42>()) and modelsConceptList<T...>(typename C::BaseConceptList()); }
} // namespace Imp
/**
* \brief Check if concept is modeled by given types
*
* This will check if the given concept is modeled by the given
* list of types. This is true if the list of types models all
* the base concepts that are refined by the given concept
* and if it satisfies all additional requirements of the latter.
*
* Notice that a concept may be defined for a list of interacting types.
* The function will check if the given list of types matches the requirements
* on the whole list. It does not check if each individual type in the list
* satisfies the concept.
*
* This concept check mechanism is inspired by the concept checking
* facility in Eric Nieblers range-v3. For more information please
* refer to the libraries project page https://github.com/ericniebler/range-v3
* or this blog entry: http://ericniebler.com/2013/11/23/concept-checking-in-c11.
* In fact the interface provided here is almost exactly the same as in range-v3.
* However the implementation differs, because range-v3 uses its own meta-programming
* library whereas our implementation is more straight forward.
*
* \tparam C The concept to check
* \tparam T The list of type to check against the concept
*
*/
template<class C, class... T>
constexpr bool models()
{
return Imp::modelsConcept<C, T...>(PriorityTag<42>());
}
// #############################################################################
// # All functions following here are implementation details for the
// # for the tupleEntriesModel() function below.
// #############################################################################
namespace Imp
{
template<class C, class First>
constexpr auto allModel()
-> std::integral_constant<bool, Concept::models<C, First>()>
{ return {}; }
template<class C, class First, class... Other>
constexpr auto allModel()
-> std::integral_constant<bool, Concept::models<C, First>() and allModel<C, Other...>()>
{ return {}; }
template<class C, class... T>
constexpr auto tupleEntriesModel(const std::tuple<T...>&)
-> decltype(allModel<C, T...>())
{ return {}; }
}
// #############################################################################
// # The method tupleEntriesModel() does the actual check if the types in a tuple
// # model a concept using the implementation details above.
// #############################################################################
template<class C, class Tuple>
constexpr auto tupleEntriesModel()
-> decltype(Imp::tupleEntriesModel<C>(std::declval<Tuple>()))
{
return {};
}
// #############################################################################
// # The following require*() functions are just helpers that allow to
// # propagate a failed check as substitution failure. This is usefull
// # inside of a concept definition.
// #############################################################################
// Helper function for use in concept definitions.
// If the passed value b is not true, the concept will to be satisfied.
template<bool b, typename std::enable_if<b, int>::type = 0>
constexpr bool requireTrue()
{
return true;
}
// Helper function for use in concept definitions.
template<class C, class... T, typename std::enable_if<models<C, T...>(), int>::type = 0>
constexpr bool requireConcept()
{
return true;
}
// Helper function for use in concept definitions.
// This allows to avoid using decltype
template<class C, class... T, typename std::enable_if<models<C, T...>(), int>::type = 0>
constexpr bool requireConcept(T&&... t)
{
return true;
}
// Helper function for use in concept definitions.
// This checks if the concept given as first type is modelled by all types in the tuple passed as argument
template<class C, class Tuple, typename std::enable_if<tupleEntriesModel<C, Tuple>(), int>::type = 0>
constexpr bool requireConceptForTupleEntries()
{
return true;
}
// Helper function for use in concept definitions.
// If the first passed type is not convertible to the second, the concept will not be satisfied.
template<class From, class To,
typename std::enable_if< std::is_convertible<From, To>::value, int>::type = 0>
constexpr bool requireConvertible()
{
return true;
}
// Helper function for use in concept definitions.
// If passed argument is not convertible to the first passed type, the concept will not be satisfied.
template<class To, class From,
typename std::enable_if< std::is_convertible<From, To>::value, int>::type = 0>
constexpr bool requireConvertible(const From&)
{
return true;
}
// Helper function for use in concept definitions.
// This will always evaluate to true. If just allow
// to turn a type into an expression. The failure happens
// already during substitution for the type argument.
template<typename T>
constexpr bool requireType()
{
return true;
}
// Helper function for use in concept definitions.
// If first passed type is not a base class of second type, the concept will not be satisfied.
template<class Base, class Derived,
typename std::enable_if< std::is_base_of<Base, Derived>::value, int>::type = 0>
constexpr bool requireBaseOf()
{
return true;
}
// Helper function for use in concept definitions.
// If first passed type is not a base class of first arguments type, the concept will not be satisfied.
template<class Base, class Derived,
typename std::enable_if< std::is_base_of<Base, Derived>::value, int>::type = 0>
constexpr bool requireBaseOf(const Derived&)
{
return true;
}
// Helper function for use in concept definitions.
// If the passed types are not the same, the concept will not be satisfied.
template<class A, class B,
typename std::enable_if< std::is_same<A, B>::value, int>::type = 0>
constexpr bool requireSameType()
{
return true;
}
}} // namespace Dune::Concept
#endif // DUNE_COMMON_CONCEPT_HH
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment