libexpr: implement actual constructors for nix::Value
Change-Id: Iebc2bb4e4ea5e93045afe47677df756de4ec4d05
This commit is contained in:
parent
c347d3df8f
commit
ddfca6e81b
4 changed files with 421 additions and 9 deletions
|
@ -494,6 +494,14 @@ std::ostream & operator<<(std::ostream & output, PrimOp & primOp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Value::Value(primop_t, PrimOp & primop)
|
||||||
|
: internalType(tPrimOp)
|
||||||
|
, primOp(&primop)
|
||||||
|
, _primop_pad(0)
|
||||||
|
{
|
||||||
|
primop.check();
|
||||||
|
}
|
||||||
|
|
||||||
PrimOp * Value::primOpAppPrimOp() const
|
PrimOp * Value::primOpAppPrimOp() const
|
||||||
{
|
{
|
||||||
Value * left = primOpApp.left;
|
Value * left = primOpApp.left;
|
||||||
|
@ -506,7 +514,6 @@ PrimOp * Value::primOpAppPrimOp() const
|
||||||
return left->primOp;
|
return left->primOp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Value::mkPrimOp(PrimOp * p)
|
void Value::mkPrimOp(PrimOp * p)
|
||||||
{
|
{
|
||||||
p->check();
|
p->check();
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
|
#include <functional>
|
||||||
|
#include <ranges>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
#include "gc-alloc.hh"
|
#include "gc-alloc.hh"
|
||||||
#include "symbol-table.hh"
|
#include "symbol-table.hh"
|
||||||
|
@ -11,6 +14,7 @@
|
||||||
#include "source-path.hh"
|
#include "source-path.hh"
|
||||||
#include "print-options.hh"
|
#include "print-options.hh"
|
||||||
#include "checked-arithmetic.hh"
|
#include "checked-arithmetic.hh"
|
||||||
|
#include "concepts.hh"
|
||||||
|
|
||||||
#include <nlohmann/json_fwd.hpp>
|
#include <nlohmann/json_fwd.hpp>
|
||||||
|
|
||||||
|
@ -132,6 +136,55 @@ class ExternalValueBase
|
||||||
|
|
||||||
std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
|
std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
|
||||||
|
|
||||||
|
extern ExprBlackHole eBlackHole;
|
||||||
|
|
||||||
|
struct NewValueAs
|
||||||
|
{
|
||||||
|
struct integer_t { };
|
||||||
|
constexpr static integer_t integer{};
|
||||||
|
|
||||||
|
struct floating_t { };
|
||||||
|
constexpr static floating_t floating{};
|
||||||
|
|
||||||
|
struct boolean_t { };
|
||||||
|
constexpr static boolean_t boolean{};
|
||||||
|
|
||||||
|
struct string_t { };
|
||||||
|
constexpr static string_t string{};
|
||||||
|
|
||||||
|
struct path_t { };
|
||||||
|
constexpr static path_t path{};
|
||||||
|
|
||||||
|
struct list_t { };
|
||||||
|
constexpr static list_t list{};
|
||||||
|
|
||||||
|
struct attrs_t { };
|
||||||
|
constexpr static attrs_t attrs{};
|
||||||
|
|
||||||
|
struct thunk_t { };
|
||||||
|
constexpr static thunk_t thunk{};
|
||||||
|
|
||||||
|
struct null_t { };
|
||||||
|
constexpr static null_t null{};
|
||||||
|
|
||||||
|
struct app_t { };
|
||||||
|
constexpr static app_t app{};
|
||||||
|
|
||||||
|
struct primop_t { };
|
||||||
|
constexpr static primop_t primop{};
|
||||||
|
|
||||||
|
struct primOpApp_t { };
|
||||||
|
constexpr static primOpApp_t primOpApp{};
|
||||||
|
|
||||||
|
struct lambda_t { };
|
||||||
|
constexpr static lambda_t lambda{};
|
||||||
|
|
||||||
|
struct external_t { };
|
||||||
|
constexpr static external_t external{};
|
||||||
|
|
||||||
|
struct blackhole_t { };
|
||||||
|
constexpr static blackhole_t blackhole{};
|
||||||
|
};
|
||||||
|
|
||||||
struct Value
|
struct Value
|
||||||
{
|
{
|
||||||
|
@ -142,6 +195,315 @@ private:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
// Discount `using NewValueAs::*;`
|
||||||
|
#define USING_VALUETYPE(name) using name = NewValueAs::name
|
||||||
|
USING_VALUETYPE(integer_t);
|
||||||
|
USING_VALUETYPE(floating_t);
|
||||||
|
USING_VALUETYPE(boolean_t);
|
||||||
|
USING_VALUETYPE(string_t);
|
||||||
|
USING_VALUETYPE(path_t);
|
||||||
|
USING_VALUETYPE(list_t);
|
||||||
|
USING_VALUETYPE(attrs_t);
|
||||||
|
USING_VALUETYPE(thunk_t);
|
||||||
|
USING_VALUETYPE(primop_t);
|
||||||
|
USING_VALUETYPE(app_t);
|
||||||
|
USING_VALUETYPE(null_t);
|
||||||
|
USING_VALUETYPE(primOpApp_t);
|
||||||
|
USING_VALUETYPE(lambda_t);
|
||||||
|
USING_VALUETYPE(external_t);
|
||||||
|
USING_VALUETYPE(blackhole_t);
|
||||||
|
#undef USING_VALUETYPE
|
||||||
|
|
||||||
|
/// Default constructor which is still used in the codebase but should not
|
||||||
|
/// be used in new code. Zero initializes its members.
|
||||||
|
[[deprecated]] Value()
|
||||||
|
: internalType(static_cast<InternalType>(0))
|
||||||
|
, _empty{ 0, 0 }
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "int", with the integral value
|
||||||
|
/// of @ref i.
|
||||||
|
Value(integer_t, NixInt i)
|
||||||
|
: internalType(tInt)
|
||||||
|
, _empty{ 0, 0 }
|
||||||
|
{
|
||||||
|
// the NixInt ctor here is is special because NixInt has a ctor too, so
|
||||||
|
// we're not allowed to have it as an anonymous aggreagte member. we do
|
||||||
|
// however still have the option to clear the data members using _empty
|
||||||
|
// and leaving the second word of data cleared by setting only integer.
|
||||||
|
integer = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "float", with the floating
|
||||||
|
/// point value of @ref f.
|
||||||
|
Value(floating_t, NixFloat f)
|
||||||
|
: internalType(tFloat)
|
||||||
|
, fpoint(f)
|
||||||
|
, _float_pad(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "bool", with the boolean
|
||||||
|
/// value of @ref b.
|
||||||
|
Value(boolean_t, bool b)
|
||||||
|
: internalType(tBool)
|
||||||
|
, boolean(b)
|
||||||
|
, _bool_pad(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "string", with the value of the
|
||||||
|
/// C-string pointed to by @ref strPtr, and optionally with an array of
|
||||||
|
/// string context pointed to by @ref contextPtr.
|
||||||
|
///
|
||||||
|
/// Neither the C-string nor the context array are copied; this constructor
|
||||||
|
/// assumes suitable memory has already been allocated (with the GC if
|
||||||
|
/// enabled), and string and context data copied into that memory.
|
||||||
|
Value(string_t, char const * strPtr, char const ** contextPtr = nullptr)
|
||||||
|
: internalType(tString)
|
||||||
|
, string({ .s = strPtr, .context = contextPtr })
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Constructx a nix language value of type "string", with a copy of the
|
||||||
|
/// string data viewed by @ref copyFrom.
|
||||||
|
///
|
||||||
|
/// The string data *is* copied from @ref copyFrom, and this constructor
|
||||||
|
/// performs a dynamic (GC) allocation to do so.
|
||||||
|
Value(string_t, std::string_view copyFrom, NixStringContext const & context = {})
|
||||||
|
: internalType(tString)
|
||||||
|
, string({ .s = gcCopyStringIfNeeded(copyFrom), .context = nullptr })
|
||||||
|
{
|
||||||
|
if (context.empty()) {
|
||||||
|
// It stays nullptr.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the context.
|
||||||
|
this->string.context = gcAllocType<char const *>(context.size() + 1);
|
||||||
|
|
||||||
|
size_t n = 0;
|
||||||
|
for (NixStringContextElem const & contextElem : context) {
|
||||||
|
this->string.context[n] = gcCopyStringIfNeeded(contextElem.to_string());
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminator sentinel.
|
||||||
|
this->string.context[n] = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructx a nix language value of type "string", with the value of the
|
||||||
|
/// C-string pointed to by @ref strPtr, and optionally with a set of string
|
||||||
|
/// context @ref context.
|
||||||
|
///
|
||||||
|
/// The C-string is not copied; this constructor assumes suitable memory
|
||||||
|
/// has already been allocated (with the GC if enabled), and string data
|
||||||
|
/// has been copied into that memory. The context data *is* copied from
|
||||||
|
/// @ref context, and this constructor performs a dynamic (GC) allocation
|
||||||
|
/// to do so.
|
||||||
|
Value(string_t, char const * strPtr, NixStringContext const & context)
|
||||||
|
: internalType(tString)
|
||||||
|
, string({ .s = strPtr, .context = nullptr })
|
||||||
|
{
|
||||||
|
if (context.empty()) {
|
||||||
|
// It stays nullptr
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the context.
|
||||||
|
this->string.context = gcAllocType<char const *>(context.size() + 1);
|
||||||
|
|
||||||
|
size_t n = 0;
|
||||||
|
for (NixStringContextElem const & contextElem : context) {
|
||||||
|
this->string.context[n] = gcCopyStringIfNeeded(contextElem.to_string());
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminator sentinel.
|
||||||
|
this->string.context[n] = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "path", with the value of the
|
||||||
|
/// C-string pointed to by @ref strPtr.
|
||||||
|
///
|
||||||
|
/// The C-string is not copied; this constructor assumes suitable memory
|
||||||
|
/// has already been allocated (with the GC if enabled), and string data
|
||||||
|
/// has been copied into that memory.
|
||||||
|
Value(path_t, char const * strPtr)
|
||||||
|
: internalType(tPath)
|
||||||
|
, _path(strPtr)
|
||||||
|
, _path_pad(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "path", with the path
|
||||||
|
/// @ref path.
|
||||||
|
///
|
||||||
|
/// The data from @ref path *is* copied, and this constructor performs a
|
||||||
|
/// dynamic (GC) allocation to do so.
|
||||||
|
Value(path_t, SourcePath const & path)
|
||||||
|
: internalType(tPath)
|
||||||
|
, _path(gcCopyStringIfNeeded(path.path.abs()))
|
||||||
|
, _path_pad(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "list", with element array
|
||||||
|
/// @ref items.
|
||||||
|
///
|
||||||
|
/// Generally, the data in @ref items is neither deep copied nor shallow
|
||||||
|
/// copied. This construct assumes the std::span @ref items is a region of
|
||||||
|
/// memory that has already been allocated (with the GC if enabled), and
|
||||||
|
/// an array of valid Value pointers has been copied into that memory.
|
||||||
|
///
|
||||||
|
/// Howver, as an implementation detail, if @ref items is only 2 items or
|
||||||
|
/// smaller, the list is stored inline, and the Value pointers in
|
||||||
|
/// @ref items are shallow copied into this structure, without dynamically
|
||||||
|
/// allocating memory.
|
||||||
|
Value(list_t, std::span<Value *> items)
|
||||||
|
{
|
||||||
|
if (items.size() == 1) {
|
||||||
|
this->internalType = tList1;
|
||||||
|
this->smallList[0] = items[0];
|
||||||
|
this->smallList[1] = nullptr;
|
||||||
|
} else if (items.size() == 2) {
|
||||||
|
this->internalType = tList2;
|
||||||
|
this->smallList[0] = items[0];
|
||||||
|
this->smallList[1] = items[1];
|
||||||
|
} else {
|
||||||
|
this->internalType = tListN;
|
||||||
|
this->bigList.size = items.size();
|
||||||
|
this->bigList.elems = items.data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "list", with an element array
|
||||||
|
/// initialized by applying @ref transformer to each element in @ref items.
|
||||||
|
///
|
||||||
|
/// This allows "in-place" construction of a nix list when some logic is
|
||||||
|
/// needed to get each Value pointer. This constructor dynamically (GC)
|
||||||
|
/// allocates memory for the size of @ref items, and the Value pointers
|
||||||
|
/// returned by @ref transformer are shallow copied into it.
|
||||||
|
template<
|
||||||
|
std::ranges::sized_range SizedIterableT,
|
||||||
|
InvocableR<Value *, typename SizedIterableT::value_type const &> TransformerT
|
||||||
|
>
|
||||||
|
Value(list_t, SizedIterableT & items, TransformerT const & transformer)
|
||||||
|
{
|
||||||
|
if (items.size() == 1) {
|
||||||
|
this->internalType = tList1;
|
||||||
|
this->smallList[0] = transformer(*items.begin());
|
||||||
|
this->smallList[1] = nullptr;
|
||||||
|
} else if (items.size() == 2) {
|
||||||
|
this->internalType = tList2;
|
||||||
|
auto it = items.begin();
|
||||||
|
this->smallList[0] = transformer(*it);
|
||||||
|
it++;
|
||||||
|
this->smallList[1] = transformer(*it);
|
||||||
|
} else {
|
||||||
|
this->internalType = tListN;
|
||||||
|
this->bigList.size = items.size();
|
||||||
|
this->bigList.elems = gcAllocType<Value *>(items.size());
|
||||||
|
auto it = items.begin();
|
||||||
|
for (size_t i = 0; i < items.size(); i++, it++) {
|
||||||
|
this->bigList.elems[i] = transformer(*it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a nix language value of the singleton type "null".
|
||||||
|
Value(null_t)
|
||||||
|
: internalType(tNull)
|
||||||
|
, _empty{0, 0}
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "set", with the attribute
|
||||||
|
/// bindings pointed to by @ref bindings.
|
||||||
|
///
|
||||||
|
/// The bindings are not not copied; this constructor assumes @ref bindings
|
||||||
|
/// has already been suitably allocated by something like nix::buildBindings.
|
||||||
|
Value(attrs_t, Bindings * bindings)
|
||||||
|
: internalType(tAttrs)
|
||||||
|
, attrs(bindings)
|
||||||
|
, _attrs_pad(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Constructs a nix language lazy delayed computation, or "thunk".
|
||||||
|
///
|
||||||
|
/// The thunk stores the environment it will be computed in @ref env, and
|
||||||
|
/// the expression that will need to be evaluated @ref expr.
|
||||||
|
Value(thunk_t, Env & env, Expr & expr)
|
||||||
|
: internalType(tThunk)
|
||||||
|
, thunk({ .env = &env, .expr = &expr })
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "lambda", which represents
|
||||||
|
/// a builtin, primitive operation ("primop"), from the primop
|
||||||
|
/// implemented by @ref primop.
|
||||||
|
Value(primop_t, PrimOp & primop);
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "lambda", which represents a
|
||||||
|
/// partially applied primop.
|
||||||
|
Value(primOpApp_t, Value & lhs, Value & rhs)
|
||||||
|
: internalType(tPrimOpApp)
|
||||||
|
, primOpApp({ .left = &lhs, .right = &rhs })
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "lambda", which represents a
|
||||||
|
/// lazy partial application of another lambda.
|
||||||
|
Value(app_t, Value & lhs, Value & rhs)
|
||||||
|
: internalType(tApp)
|
||||||
|
, app({ .left = &lhs, .right = &rhs })
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "external", which is only used
|
||||||
|
/// by plugins. Do any existing plugins even use this mechanism?
|
||||||
|
Value(external_t, ExternalValueBase & external)
|
||||||
|
: internalType(tExternal)
|
||||||
|
, external(&external)
|
||||||
|
, _external_pad(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Constructs a nix language value of type "lambda", which represents a
|
||||||
|
/// run of the mill lambda defined in nix code.
|
||||||
|
///
|
||||||
|
/// This takes the environment the lambda is closed over @ref env, and
|
||||||
|
/// the lambda expression itself @ref lambda, which will not be evaluated
|
||||||
|
/// until it is applied.
|
||||||
|
Value(lambda_t, Env & env, ExprLambda & lambda)
|
||||||
|
: internalType(tLambda)
|
||||||
|
, lambda({ .env = &env, .fun = &lambda })
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Constructs an evil thunk, whose evaluation represents infinite recursion.
|
||||||
|
explicit Value(blackhole_t)
|
||||||
|
: internalType(tThunk)
|
||||||
|
, thunk({ .env = nullptr, .expr = reinterpret_cast<Expr *>(&eBlackHole) })
|
||||||
|
{ }
|
||||||
|
|
||||||
|
Value(Value const & rhs) = default;
|
||||||
|
|
||||||
|
/// Move constructor. Does the same thing as the copy constructor, but
|
||||||
|
/// also zeroes out the other Value.
|
||||||
|
Value(Value && rhs)
|
||||||
|
: internalType(rhs.internalType)
|
||||||
|
, _empty{ 0, 0 }
|
||||||
|
{
|
||||||
|
*this = std::move(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value & operator=(Value const & rhs) = default;
|
||||||
|
|
||||||
|
/// Move assignment operator.
|
||||||
|
/// Does the same thing as the copy assignment operator, but also zeroes out
|
||||||
|
/// the rhs.
|
||||||
|
inline Value & operator=(Value && rhs)
|
||||||
|
{
|
||||||
|
*this = static_cast<const Value &>(rhs);
|
||||||
|
if (this != &rhs) {
|
||||||
|
// Kill `rhs`, because non-destructive move lol.
|
||||||
|
rhs.internalType = static_cast<InternalType>(0);
|
||||||
|
rhs._empty[0] = 0;
|
||||||
|
rhs._empty[1] = 0;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
void print(EvalState &state, std::ostream &str, PrintOptions options = PrintOptions {});
|
void print(EvalState &state, std::ostream &str, PrintOptions options = PrintOptions {});
|
||||||
|
|
||||||
// Functions needed to distinguish the type
|
// Functions needed to distinguish the type
|
||||||
|
@ -160,8 +522,15 @@ public:
|
||||||
|
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
|
/// Dummy field, which takes up as much space as the largest union variants
|
||||||
|
/// to set the union's memory to zeroed memory.
|
||||||
|
uintptr_t _empty[2];
|
||||||
|
|
||||||
NixInt integer;
|
NixInt integer;
|
||||||
|
struct {
|
||||||
bool boolean;
|
bool boolean;
|
||||||
|
uintptr_t _bool_pad;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strings in the evaluator carry a so-called `context` which
|
* Strings in the evaluator carry a so-called `context` which
|
||||||
|
@ -190,8 +559,14 @@ public:
|
||||||
const char * * context; // must be in sorted order
|
const char * * context; // must be in sorted order
|
||||||
} string;
|
} string;
|
||||||
|
|
||||||
|
struct {
|
||||||
const char * _path;
|
const char * _path;
|
||||||
|
uintptr_t _path_pad;
|
||||||
|
};
|
||||||
|
struct {
|
||||||
Bindings * attrs;
|
Bindings * attrs;
|
||||||
|
uintptr_t _attrs_pad;
|
||||||
|
};
|
||||||
struct {
|
struct {
|
||||||
size_t size;
|
size_t size;
|
||||||
Value * * elems;
|
Value * * elems;
|
||||||
|
@ -208,12 +583,21 @@ public:
|
||||||
Env * env;
|
Env * env;
|
||||||
ExprLambda * fun;
|
ExprLambda * fun;
|
||||||
} lambda;
|
} lambda;
|
||||||
|
struct {
|
||||||
PrimOp * primOp;
|
PrimOp * primOp;
|
||||||
|
uintptr_t _primop_pad;
|
||||||
|
};
|
||||||
struct {
|
struct {
|
||||||
Value * left, * right;
|
Value * left, * right;
|
||||||
} primOpApp;
|
} primOpApp;
|
||||||
|
struct {
|
||||||
ExternalValueBase * external;
|
ExternalValueBase * external;
|
||||||
|
uintptr_t _external_pad;
|
||||||
|
};
|
||||||
|
struct {
|
||||||
NixFloat fpoint;
|
NixFloat fpoint;
|
||||||
|
uintptr_t _float_pad;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -449,8 +833,6 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
extern ExprBlackHole eBlackHole;
|
|
||||||
|
|
||||||
bool Value::isBlackhole() const
|
bool Value::isBlackhole() const
|
||||||
{
|
{
|
||||||
return internalType == tThunk && thunk.expr == (Expr*) &eBlackHole;
|
return internalType == tThunk && thunk.expr == (Expr*) &eBlackHole;
|
||||||
|
|
22
src/libutil/concepts.hh
Normal file
22
src/libutil/concepts.hh
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
/// @file Defines C++ 20 concepts that std doesn't have.
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace nix
|
||||||
|
{
|
||||||
|
|
||||||
|
/// Like std::invocable<>, but also constrains the return type as well.
|
||||||
|
///
|
||||||
|
/// Somehow, there is no std concept to do this, even though there is a type trait
|
||||||
|
/// for it.
|
||||||
|
///
|
||||||
|
/// @tparam CallableT The type you want to constrain to be callable, and to return
|
||||||
|
/// @p ReturnT when called with @p Args as arguments.
|
||||||
|
///
|
||||||
|
/// @tparam ReturnT The type the callable should return when called.
|
||||||
|
/// @tparam Args The arguments the callable should accept to return @p ReturnT.
|
||||||
|
template<typename CallableT, typename ReturnT, typename ...Args>
|
||||||
|
concept InvocableR = std::is_invocable_r_v<ReturnT, CallableT, Args...>;
|
||||||
|
|
||||||
|
}
|
|
@ -58,6 +58,7 @@ libutil_headers = files(
|
||||||
'comparator.hh',
|
'comparator.hh',
|
||||||
'compression.hh',
|
'compression.hh',
|
||||||
'compute-levels.hh',
|
'compute-levels.hh',
|
||||||
|
'concepts.hh',
|
||||||
'config-impl.hh',
|
'config-impl.hh',
|
||||||
'config.hh',
|
'config.hh',
|
||||||
'current-process.hh',
|
'current-process.hh',
|
||||||
|
|
Loading…
Reference in a new issue