Files
snmalloc/src/ds/concept.h
Nathaniel Wesley Filardo 3c14a7ddf1 NFC: Concepts and fixed-pointing interact poorly
Fortunately, C++ taketh away and C++ giveth, both, so here we are: a way to
detect if we're in the middle of definining a type that uses itself as a
template parameter in a way that flows into a concept check and, if so,
short-circuit out of the need to actually do any checks.  Wonders never cease.
2021-08-26 16:53:52 +01:00

72 lines
2.9 KiB
C++

#pragma once
#include <type_traits>
/**
* C++20 concepts are referenced as if they were types in declarations within
* template parameters (e.g. "template<FooConcept Foo> ..."). That is, they
* take the place of the "typename"/"class" keyword on template parameters.
* If the compiler understands concepts, this macro expands as its argument;
* otherwise, it expands to the keyword "typename", so snmalloc templates that
* use concept-qualified parameters should use this to remain compatible across
* C++ versions: "template<SNMALLOC_CONCEPT(FooConcept) Foo>"
*/
#ifdef __cpp_concepts
# define SNMALLOC_CONCEPT(c) c
#else
# define SNMALLOC_CONCEPT(c) typename
#endif
#ifdef __cpp_concepts
namespace snmalloc
{
/**
* C++20 concepts are more than just new syntax; there's a new support
* library specified as well. As C++20 is quite new, however, there are some
* environments, notably Clang, that understand the syntax but do not yet
* offer the library. Fortunately, alternate pronouciations are possible.
*/
# ifdef _cpp_lib_concepts
/**
* ConceptSame<T,U> is true if T and U are the same type and false otherwise.
* When specifying a concept, use ConceptSame<U> to indicate that an
* expression must evaluate precisely to the type U.
*/
template<typename T, typename U>
concept ConceptSame = std::same_as<T, U>;
# else
template<typename T, typename U>
concept ConceptSame = std::is_same<T, U>::value;
# endif
/**
* Some of the types in snmalloc are circular in their definition and use
* templating as a lazy language to carefully tie knots and only pull on the
* whole mess once it's assembled. Unfortunately, concepts amount to eagerly
* demanding the result of the computation. If concepts come into play during
* the circular definition, they may see an incomplete type and so fail (with
* "incomplete type ... used in type trait expression" or similar). However,
* it turns out that SFINAE gives us a way to detect whether a template
* parameter refers to an incomplete type, and short circuit evaluation means
* we can bail on concept checking if we find ourselves in this situation.
*
* See https://devblogs.microsoft.com/oldnewthing/20190710-00/?p=102678
*
* Unfortunately, C++20 concepts are not first-order things and, in
* particular, cannot themselves be template parameters. So while we would
* love to write a generic Lazy combinator,
*
* template<template<typename> concept C, typename T>
* concept Lazy = !is_type_complete_v<T> || C<T>();
*
* this will instead have to be inlined at every definition (and referred to
* explicitly at call sites) until C++23 or later.
*/
template<typename, typename = void>
constexpr bool is_type_complete_v = false;
template<typename T>
constexpr bool is_type_complete_v<T, std::void_t<decltype(sizeof(T))>> = true;
} // namespace snmalloc
#endif