aboutsummaryrefslogtreecommitdiff
path: root/src/libutil
diff options
context:
space:
mode:
Diffstat (limited to 'src/libutil')
-rw-r--r--src/libutil/compression.cc6
-rw-r--r--src/libutil/compression.hh2
-rw-r--r--src/libutil/config.cc20
-rw-r--r--src/libutil/config.hh19
-rw-r--r--src/libutil/experimental-features.cc231
-rw-r--r--src/libutil/experimental-features.hh37
-rw-r--r--src/libutil/hash.cc2
-rw-r--r--src/libutil/hash.hh2
-rw-r--r--src/libutil/serialise.cc45
-rw-r--r--src/libutil/serialise.hh14
-rw-r--r--src/libutil/tests/config.cc48
-rw-r--r--src/libutil/util.hh10
12 files changed, 369 insertions, 67 deletions
diff --git a/src/libutil/compression.cc b/src/libutil/compression.cc
index 89180e7a7..ba0847cde 100644
--- a/src/libutil/compression.cc
+++ b/src/libutil/compression.cc
@@ -23,7 +23,7 @@ struct ChunkedCompressionSink : CompressionSink
{
uint8_t outbuf[32 * 1024];
- void write(std::string_view data) override
+ void writeUnbuffered(std::string_view data) override
{
const size_t CHUNK_SIZE = sizeof(outbuf) << 2;
while (!data.empty()) {
@@ -103,7 +103,7 @@ struct ArchiveCompressionSink : CompressionSink
throw Error(reason, archive_error_string(this->archive));
}
- void write(std::string_view data) override
+ void writeUnbuffered(std::string_view data) override
{
ssize_t result = archive_write_data(archive, data.data(), data.length());
if (result <= 0) check(result);
@@ -136,7 +136,7 @@ struct NoneSink : CompressionSink
warn("requested compression level '%d' not supported by compression method 'none'", level);
}
void finish() override { flush(); }
- void write(std::string_view data) override { nextSink(data); }
+ void writeUnbuffered(std::string_view data) override { nextSink(data); }
};
struct BrotliDecompressionSink : ChunkedCompressionSink
diff --git a/src/libutil/compression.hh b/src/libutil/compression.hh
index 3892831c2..4e53a7b3c 100644
--- a/src/libutil/compression.hh
+++ b/src/libutil/compression.hh
@@ -12,7 +12,7 @@ namespace nix {
struct CompressionSink : BufferedSink, FinishSink
{
using BufferedSink::operator ();
- using BufferedSink::write;
+ using BufferedSink::writeUnbuffered;
using FinishSink::finish;
};
diff --git a/src/libutil/config.cc b/src/libutil/config.cc
index 8d63536d6..a42f3a849 100644
--- a/src/libutil/config.cc
+++ b/src/libutil/config.cc
@@ -70,17 +70,10 @@ void AbstractConfig::reapplyUnknownSettings()
set(s.first, s.second);
}
-// Whether we should process the option. Excludes aliases, which are handled elsewhere, and disabled features.
-static bool applicable(const Config::SettingData & sd)
-{
- return !sd.isAlias
- && experimentalFeatureSettings.isEnabled(sd.setting->experimentalFeature);
-}
-
void Config::getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly)
{
for (auto & opt : _settings)
- if (applicable(opt.second) && (!overriddenOnly || opt.second.setting->overridden))
+ if (!opt.second.isAlias && (!overriddenOnly || opt.second.setting->overridden))
res.emplace(opt.first, SettingInfo{opt.second.setting->to_string(), opt.second.setting->description});
}
@@ -154,7 +147,7 @@ nlohmann::json Config::toJSON()
{
auto res = nlohmann::json::object();
for (auto & s : _settings)
- if (applicable(s.second))
+ if (!s.second.isAlias)
res.emplace(s.first, s.second.setting->toJSON());
return res;
}
@@ -163,7 +156,7 @@ std::string Config::toKeyValue()
{
auto res = std::string();
for (auto & s : _settings)
- if (applicable(s.second))
+ if (s.second.isAlias)
res += fmt("%s = %s\n", s.first, s.second.setting->to_string());
return res;
}
@@ -171,9 +164,6 @@ std::string Config::toKeyValue()
void Config::convertToArgs(Args & args, const std::string & category)
{
for (auto & s : _settings) {
- /* We do include args for settings gated on disabled
- experimental-features. The args themselves however will also be
- gated on any experimental feature the underlying setting is. */
if (!s.second.isAlias)
s.second.setting->convertToArg(args, category);
}
@@ -201,6 +191,10 @@ std::map<std::string, nlohmann::json> AbstractSetting::toJSONObject()
std::map<std::string, nlohmann::json> obj;
obj.emplace("description", description);
obj.emplace("aliases", aliases);
+ if (experimentalFeature)
+ obj.emplace("experimentalFeature", *experimentalFeature);
+ else
+ obj.emplace("experimentalFeature", nullptr);
return obj;
}
diff --git a/src/libutil/config.hh b/src/libutil/config.hh
index 3c1d70294..162626791 100644
--- a/src/libutil/config.hh
+++ b/src/libutil/config.hh
@@ -371,8 +371,23 @@ extern GlobalConfig globalConfig;
struct ExperimentalFeatureSettings : Config {
- Setting<std::set<ExperimentalFeature>> experimentalFeatures{this, {}, "experimental-features",
- "Experimental Nix features to enable."};
+ Setting<std::set<ExperimentalFeature>> experimentalFeatures{
+ this, {}, "experimental-features",
+ R"(
+ Experimental features that are enabled.
+
+ Example:
+
+ ```
+ experimental-features = nix-command flakes
+ ```
+
+ The following experimental features are available:
+
+ {{#include experimental-features-shortlist.md}}
+
+ Experimental features are [further documented in the manual](@docroot@/contributing/experimental-features.md).
+ )"};
/**
* Check whether the given experimental feature is enabled.
diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc
index 58d762ebb..5b4418714 100644
--- a/src/libutil/experimental-features.cc
+++ b/src/libutil/experimental-features.cc
@@ -5,29 +5,209 @@
namespace nix {
-std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
- { Xp::CaDerivations, "ca-derivations" },
- { Xp::ImpureDerivations, "impure-derivations" },
- { Xp::Flakes, "flakes" },
- { Xp::NixCommand, "nix-command" },
- { Xp::RecursiveNix, "recursive-nix" },
- { Xp::NoUrlLiterals, "no-url-literals" },
- { Xp::FetchClosure, "fetch-closure" },
- { Xp::ReplFlake, "repl-flake" },
- { Xp::AutoAllocateUids, "auto-allocate-uids" },
- { Xp::Cgroups, "cgroups" },
- { Xp::DiscardReferences, "discard-references" },
+struct ExperimentalFeatureDetails
+{
+ ExperimentalFeature tag;
+ std::string_view name;
+ std::string_view description;
};
+constexpr std::array<ExperimentalFeatureDetails, 11> xpFeatureDetails = {{
+ {
+ .tag = Xp::CaDerivations,
+ .name = "ca-derivations",
+ .description = R"(
+ Allow derivations to be content-addressed in order to prevent
+ rebuilds when changes to the derivation do not result in changes to
+ the derivation's output. See
+ [__contentAddressed](@docroot@/language/advanced-attributes.md#adv-attr-__contentAddressed)
+ for details.
+ )",
+ },
+ {
+ .tag = Xp::ImpureDerivations,
+ .name = "impure-derivations",
+ .description = R"(
+ Allow derivations to produce non-fixed outputs by setting the
+ `__impure` derivation attribute to `true`. An impure derivation can
+ have differing outputs each time it is built.
+
+ Example:
+
+ ```
+ derivation {
+ name = "impure";
+ builder = /bin/sh;
+ __impure = true; # mark this derivation as impure
+ args = [ "-c" "read -n 10 random < /dev/random; echo $random > $out" ];
+ system = builtins.currentSystem;
+ }
+ ```
+
+ Each time this derivation is built, it can produce a different
+ output (as the builder outputs random bytes to `$out`). Impure
+ derivations also have access to the network, and only fixed-output
+ or other impure derivations can rely on impure derivations. Finally,
+ an impure derivation cannot also be
+ [content-addressed](#xp-feature-ca-derivations).
+ )",
+ },
+ {
+ .tag = Xp::Flakes,
+ .name = "flakes",
+ .description = R"(
+ Enable flakes. See the manual entry for [`nix
+ flake`](@docroot@/command-ref/new-cli/nix3-flake.md) for details.
+ )",
+ },
+ {
+ .tag = Xp::NixCommand,
+ .name = "nix-command",
+ .description = R"(
+ Enable the new `nix` subcommands. See the manual on
+ [`nix`](@docroot@/command-ref/new-cli/nix.md) for details.
+ )",
+ },
+ {
+ .tag = Xp::RecursiveNix,
+ .name = "recursive-nix",
+ .description = R"(
+ Allow derivation builders to call Nix, and thus build derivations
+ recursively.
+
+ Example:
+
+ ```
+ with import <nixpkgs> {};
+
+ runCommand "foo"
+ {
+ buildInputs = [ nix jq ];
+ NIX_PATH = "nixpkgs=${<nixpkgs>}";
+ }
+ ''
+ hello=$(nix-build -E '(import <nixpkgs> {}).hello.overrideDerivation (args: { name = "recursive-hello"; })')
+
+ mkdir -p $out/bin
+ ln -s $hello/bin/hello $out/bin/hello
+ ''
+ ```
+
+ An important restriction on recursive builders is disallowing
+ arbitrary substitutions. For example, running
+
+ ```
+ nix-store -r /nix/store/kmwd1hq55akdb9sc7l3finr175dajlby-hello-2.10
+ ```
+
+ in the above `runCommand` script would be disallowed, as this could
+ lead to derivations with hidden dependencies or breaking
+ reproducibility by relying on the current state of the Nix store. An
+ exception would be if
+ `/nix/store/kmwd1hq55akdb9sc7l3finr175dajlby-hello-2.10` were
+ already in the build inputs or built by a previous recursive Nix
+ call.
+ )",
+ },
+ {
+ .tag = Xp::NoUrlLiterals,
+ .name = "no-url-literals",
+ .description = R"(
+ Disallow unquoted URLs as part of the Nix language syntax. The Nix
+ language allows for URL literals, like so:
+
+ ```
+ $ nix repl
+ Welcome to Nix 2.15.0. Type :? for help.
+
+ nix-repl> http://foo
+ "http://foo"
+ ```
+
+ But enabling this experimental feature will cause the Nix parser to
+ throw an error when encountering a URL literal:
+
+ ```
+ $ nix repl --extra-experimental-features 'no-url-literals'
+ Welcome to Nix 2.15.0. Type :? for help.
+
+ nix-repl> http://foo
+ error: URL literals are disabled
+
+ at «string»:1:1:
+
+ 1| http://foo
+ | ^
+
+ ```
+
+ While this is currently an experimental feature, unquoted URLs are
+ being deprecated and their usage is discouraged.
+
+ The reason is that, as opposed to path literals, URLs have no
+ special properties that distinguish them from regular strings, URLs
+ containing parameters have to be quoted anyway, and unquoted URLs
+ may confuse external tooling.
+ )",
+ },
+ {
+ .tag = Xp::FetchClosure,
+ .name = "fetch-closure",
+ .description = R"(
+ Enable the use of the [`fetchClosure`](@docroot@/language/builtins.md#builtins-fetchClosure) built-in function in the Nix language.
+ )",
+ },
+ {
+ .tag = Xp::ReplFlake,
+ .name = "repl-flake",
+ .description = R"(
+ Allow passing [installables](@docroot@/command-ref/new-cli/nix.md#installables) to `nix repl`, making its interface consistent with the other experimental commands.
+ )",
+ },
+ {
+ .tag = Xp::AutoAllocateUids,
+ .name = "auto-allocate-uids",
+ .description = R"(
+ Allows Nix to automatically pick UIDs for builds, rather than creating
+ `nixbld*` user accounts. See the [`auto-allocate-uids`](#conf-auto-allocate-uids) setting for details.
+ )",
+ },
+ {
+ .tag = Xp::Cgroups,
+ .name = "cgroups",
+ .description = R"(
+ Allows Nix to execute builds inside cgroups. See
+ the [`use-cgroups`](#conf-use-cgroups) setting for details.
+ )",
+ },
+ {
+ .tag = Xp::DiscardReferences,
+ .name = "discard-references",
+ .description = R"(
+ Allow the use of the [`unsafeDiscardReferences`](@docroot@/language/advanced-attributes.html#adv-attr-unsafeDiscardReferences) attribute in derivations
+ that use [structured attributes](@docroot@/language/advanced-attributes.html#adv-attr-structuredAttrs). This disables scanning of outputs for
+ runtime dependencies.
+ )",
+ },
+}};
+
+static_assert(
+ []() constexpr {
+ for (auto [index, feature] : enumerate(xpFeatureDetails))
+ if (index != (size_t)feature.tag)
+ return false;
+ return true;
+ }(),
+ "array order does not match enum tag order");
+
const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)
{
using ReverseXpMap = std::map<std::string_view, ExperimentalFeature>;
- static auto reverseXpMap = []()
- {
+ static std::unique_ptr<ReverseXpMap> reverseXpMap = []() {
auto reverseXpMap = std::make_unique<ReverseXpMap>();
- for (auto & [feature, name] : stringifiedXpFeatures)
- (*reverseXpMap)[name] = feature;
+ for (auto & xpFeature : xpFeatureDetails)
+ (*reverseXpMap)[xpFeature.name] = xpFeature.tag;
return reverseXpMap;
}();
@@ -37,20 +217,27 @@ const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::str
return std::nullopt;
}
-std::string_view showExperimentalFeature(const ExperimentalFeature feature)
+std::string_view showExperimentalFeature(const ExperimentalFeature tag)
+{
+ assert((size_t)tag < xpFeatureDetails.size());
+ return xpFeatureDetails[(size_t)tag].name;
+}
+
+nlohmann::json documentExperimentalFeatures()
{
- const auto ret = get(stringifiedXpFeatures, feature);
- assert(ret);
- return *ret;
+ StringMap res;
+ for (auto & xpFeature : xpFeatureDetails)
+ res[std::string { xpFeature.name }] =
+ trim(stripIndentation(xpFeature.description));
+ return (nlohmann::json) res;
}
std::set<ExperimentalFeature> parseFeatures(const std::set<std::string> & rawFeatures)
{
std::set<ExperimentalFeature> res;
- for (auto & rawFeature : rawFeatures) {
+ for (auto & rawFeature : rawFeatures)
if (auto feature = parseExperimentalFeature(rawFeature))
res.insert(*feature);
- }
return res;
}
diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh
index 5948ad7ad..8ef66263a 100644
--- a/src/libutil/experimental-features.hh
+++ b/src/libutil/experimental-features.hh
@@ -11,8 +11,9 @@ namespace nix {
/**
* The list of available experimental features.
*
- * If you update this, don’t forget to also change the map defining their
- * string representation in the corresponding `.cc` file.
+ * If you update this, don’t forget to also change the map defining
+ * their string representation and documentation in the corresponding
+ * `.cc` file as well.
*/
enum struct ExperimentalFeature
{
@@ -34,26 +35,52 @@ enum struct ExperimentalFeature
*/
using Xp = ExperimentalFeature;
+/**
+ * Parse an experimental feature (enum value) from its name. Experimental
+ * feature flag names are hyphenated and do not contain spaces.
+ */
const std::optional<ExperimentalFeature> parseExperimentalFeature(
const std::string_view & name);
+
+/**
+ * Show the name of an experimental feature. This is the opposite of
+ * parseExperimentalFeature().
+ */
std::string_view showExperimentalFeature(const ExperimentalFeature);
+/**
+ * Compute the documentation of all experimental features.
+ *
+ * See `doc/manual` for how this information is used.
+ */
+nlohmann::json documentExperimentalFeatures();
+
+/**
+ * Shorthand for `str << showExperimentalFeature(feature)`.
+ */
std::ostream & operator<<(
std::ostream & str,
const ExperimentalFeature & feature);
/**
- * Parse a set of strings to the corresponding set of experimental features,
- * ignoring (but warning for) any unkwown feature.
+ * Parse a set of strings to the corresponding set of experimental
+ * features, ignoring (but warning for) any unknown feature.
*/
std::set<ExperimentalFeature> parseFeatures(const std::set<std::string> &);
+/**
+ * An experimental feature was required for some (experimental)
+ * operation, but was not enabled.
+ */
class MissingExperimentalFeature : public Error
{
public:
+ /**
+ * The experimental feature that was required but not enabled.
+ */
ExperimentalFeature missingFeature;
- MissingExperimentalFeature(ExperimentalFeature);
+ MissingExperimentalFeature(ExperimentalFeature missingFeature);
};
/**
diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc
index 02bddc8d9..2c36d9d94 100644
--- a/src/libutil/hash.cc
+++ b/src/libutil/hash.cc
@@ -343,7 +343,7 @@ HashSink::~HashSink()
delete ctx;
}
-void HashSink::write(std::string_view data)
+void HashSink::writeUnbuffered(std::string_view data)
{
bytes += data.size();
update(ht, *ctx, data);
diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh
index be1fdba2a..ae3ee40f4 100644
--- a/src/libutil/hash.hh
+++ b/src/libutil/hash.hh
@@ -197,7 +197,7 @@ public:
HashSink(HashType ht);
HashSink(const HashSink & h);
~HashSink();
- void write(std::string_view data) override;
+ void writeUnbuffered(std::string_view data) override;
HashResult finish() override;
HashResult currentHash();
};
diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc
index 7476e6f6c..3d5121a19 100644
--- a/src/libutil/serialise.cc
+++ b/src/libutil/serialise.cc
@@ -20,7 +20,7 @@ void BufferedSink::operator () (std::string_view data)
buffer size. */
if (bufPos + data.size() >= bufSize) {
flush();
- write(data);
+ writeUnbuffered(data);
break;
}
/* Otherwise, copy the bytes to the buffer. Flush the buffer
@@ -38,7 +38,7 @@ void BufferedSink::flush()
if (bufPos == 0) return;
size_t n = bufPos;
bufPos = 0; // don't trigger the assert() in ~BufferedSink()
- write({buffer.get(), n});
+ writeUnbuffered({buffer.get(), n});
}
@@ -48,7 +48,7 @@ FdSink::~FdSink()
}
-void FdSink::write(std::string_view data)
+void FdSink::writeUnbuffered(std::string_view data)
{
written += data.size();
try {
@@ -186,6 +186,22 @@ static DefaultStackAllocator defaultAllocatorSingleton;
StackAllocator *StackAllocator::defaultAllocator = &defaultAllocatorSingleton;
+std::shared_ptr<void> (*create_coro_gc_hook)() = []() -> std::shared_ptr<void> {
+ return {};
+};
+
+/* This class is used for entry and exit hooks on coroutines */
+class CoroutineContext {
+ /* Disable GC when entering the coroutine without the boehm patch,
+ * since it doesn't find the main thread stack in this case.
+ * std::shared_ptr<void> performs type-erasure, so it will call the right
+ * deleter. */
+ const std::shared_ptr<void> coro_gc_hook = create_coro_gc_hook();
+public:
+ CoroutineContext() {};
+ ~CoroutineContext() {};
+};
+
std::unique_ptr<FinishSink> sourceToSink(std::function<void(Source &)> fun)
{
struct SourceToSink : FinishSink
@@ -206,7 +222,8 @@ std::unique_ptr<FinishSink> sourceToSink(std::function<void(Source &)> fun)
if (in.empty()) return;
cur = in;
- if (!coro)
+ if (!coro) {
+ CoroutineContext ctx;
coro = coro_t::push_type(VirtualStackAllocator{}, [&](coro_t::pull_type & yield) {
LambdaSource source([&](char *out, size_t out_len) {
if (cur.empty()) {
@@ -223,17 +240,24 @@ std::unique_ptr<FinishSink> sourceToSink(std::function<void(Source &)> fun)
});
fun(source);
});
+ }
if (!*coro) { abort(); }
- if (!cur.empty()) (*coro)(false);
+ if (!cur.empty()) {
+ CoroutineContext ctx;
+ (*coro)(false);
+ }
}
void finish() override
{
if (!coro) return;
if (!*coro) abort();
- (*coro)(true);
+ {
+ CoroutineContext ctx;
+ (*coro)(true);
+ }
if (*coro) abort();
}
};
@@ -264,18 +288,23 @@ std::unique_ptr<Source> sinkToSource(
size_t read(char * data, size_t len) override
{
- if (!coro)
+ if (!coro) {
+ CoroutineContext ctx;
coro = coro_t::pull_type(VirtualStackAllocator{}, [&](coro_t::push_type & yield) {
LambdaSink sink([&](std::string_view data) {
if (!data.empty()) yield(std::string(data));
});
fun(sink);
});
+ }
if (!*coro) { eof(); abort(); }
if (pos == cur.size()) {
- if (!cur.empty()) (*coro)();
+ if (!cur.empty()) {
+ CoroutineContext ctx;
+ (*coro)();
+ }
cur = coro->get();
pos = 0;
}
diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh
index 2cf527023..333c254ea 100644
--- a/src/libutil/serialise.hh
+++ b/src/libutil/serialise.hh
@@ -53,7 +53,9 @@ struct BufferedSink : virtual Sink
void flush();
- virtual void write(std::string_view data) = 0;
+protected:
+
+ virtual void writeUnbuffered(std::string_view data) = 0;
};
@@ -133,7 +135,7 @@ struct FdSink : BufferedSink
~FdSink();
- void write(std::string_view data) override;
+ void writeUnbuffered(std::string_view data) override;
bool good() override;
@@ -520,7 +522,7 @@ struct FramedSink : nix::BufferedSink
}
}
- void write(std::string_view data) override
+ void writeUnbuffered(std::string_view data) override
{
/* Don't send more data if the remote has
encountered an error. */
@@ -551,4 +553,10 @@ struct StackAllocator {
static StackAllocator *defaultAllocator;
};
+/* Disabling GC when entering a coroutine (without the boehm patch).
+ mutable to avoid boehm gc dependency in libutil.
+ */
+extern std::shared_ptr<void> (*create_coro_gc_hook)();
+
+
}
diff --git a/src/libutil/tests/config.cc b/src/libutil/tests/config.cc
index 8be6730dd..f250e934e 100644
--- a/src/libutil/tests/config.cc
+++ b/src/libutil/tests/config.cc
@@ -156,12 +156,54 @@ namespace nix {
}
TEST(Config, toJSONOnNonEmptyConfig) {
+ using nlohmann::literals::operator "" _json;
Config config;
- std::map<std::string, Config::SettingInfo> settings;
- Setting<std::string> setting{&config, "", "name-of-the-setting", "description"};
+ Setting<std::string> setting{
+ &config,
+ "",
+ "name-of-the-setting",
+ "description",
+ };
+ setting.assign("value");
+
+ ASSERT_EQ(config.toJSON(),
+ R"#({
+ "name-of-the-setting": {
+ "aliases": [],
+ "defaultValue": "",
+ "description": "description\n",
+ "documentDefault": true,
+ "value": "value",
+ "experimentalFeature": null
+ }
+ })#"_json);
+ }
+
+ TEST(Config, toJSONOnNonEmptyConfigWithExperimentalSetting) {
+ using nlohmann::literals::operator "" _json;
+ Config config;
+ Setting<std::string> setting{
+ &config,
+ "",
+ "name-of-the-setting",
+ "description",
+ {},
+ true,
+ Xp::Flakes,
+ };
setting.assign("value");
- ASSERT_EQ(config.toJSON().dump(), R"#({"name-of-the-setting":{"aliases":[],"defaultValue":"","description":"description\n","documentDefault":true,"value":"value"}})#");
+ ASSERT_EQ(config.toJSON(),
+ R"#({
+ "name-of-the-setting": {
+ "aliases": [],
+ "defaultValue": "",
+ "description": "description\n",
+ "documentDefault": true,
+ "value": "value",
+ "experimentalFeature": "flakes"
+ }
+ })#"_json);
}
TEST(Config, setSettingAlias) {
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 6ff9d2524..040fed68f 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -936,16 +936,16 @@ constexpr auto enumerate(T && iterable)
{
size_t i;
TIter iter;
- bool operator != (const iterator & other) const { return iter != other.iter; }
- void operator ++ () { ++i; ++iter; }
- auto operator * () const { return std::tie(i, *iter); }
+ constexpr bool operator != (const iterator & other) const { return iter != other.iter; }
+ constexpr void operator ++ () { ++i; ++iter; }
+ constexpr auto operator * () const { return std::tie(i, *iter); }
};
struct iterable_wrapper
{
T iterable;
- auto begin() { return iterator{ 0, std::begin(iterable) }; }
- auto end() { return iterator{ 0, std::end(iterable) }; }
+ constexpr auto begin() { return iterator{ 0, std::begin(iterable) }; }
+ constexpr auto end() { return iterator{ 0, std::end(iterable) }; }
};
return iterable_wrapper{ std::forward<T>(iterable) };