mirror of
https://github.com/NixOS/nix.git
synced 2025-11-22 02:09:36 +01:00
Merge commit 'b24757f08a' into sync-2.24.2
This commit is contained in:
commit
c1d27763c6
330 changed files with 4907 additions and 1814 deletions
|
|
@ -29,6 +29,7 @@ struct Completions final : AddCompletions
|
|||
*/
|
||||
class RootArgs : virtual public Args
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* @brief The command's "working directory", but only set when top level.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
#include <vector>
|
||||
#include <limits>
|
||||
|
||||
#include "error.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
|
|
@ -30,7 +32,7 @@ private:
|
|||
auto & addChunk()
|
||||
{
|
||||
if (size_ >= std::numeric_limits<uint32_t>::max() - ChunkSize)
|
||||
abort();
|
||||
unreachable();
|
||||
chunks.emplace_back();
|
||||
chunks.back().reserve(ChunkSize);
|
||||
return chunks.back();
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
|
|||
{
|
||||
args.addFlag({
|
||||
.longName = name,
|
||||
.aliases = aliases,
|
||||
.description = fmt("Set the `%s` setting.", name),
|
||||
.category = category,
|
||||
.labels = {"value"},
|
||||
|
|
@ -91,6 +92,7 @@ void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
|
|||
if (isAppendable())
|
||||
args.addFlag({
|
||||
.longName = "extra-" + name,
|
||||
.aliases = aliases,
|
||||
.description = fmt("Append to the `%s` setting.", name),
|
||||
.category = category,
|
||||
.labels = {"value"},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "config.hh"
|
||||
#include "args.hh"
|
||||
#include "abstract-setting-to-json.hh"
|
||||
#include "environment-variables.hh"
|
||||
#include "experimental-features.hh"
|
||||
#include "util.hh"
|
||||
#include "file-system.hh"
|
||||
|
|
@ -170,9 +171,18 @@ void AbstractConfig::applyConfig(const std::string & contents, const std::string
|
|||
set(name, value);
|
||||
|
||||
// Then apply other settings
|
||||
for (const auto & [name, value] : parsedContents)
|
||||
if (name != "experimental-features" && name != "extra-experimental-features")
|
||||
// XXX: NIX_PATH must override the regular setting! This is done in `initGC()`
|
||||
// Environment variables overriding settings should probably be part of the Config mechanism,
|
||||
// but at the time of writing it's not worth building that for just one thing
|
||||
for (const auto & [name, value] : parsedContents) {
|
||||
if (name != "experimental-features" && name != "extra-experimental-features") {
|
||||
if ((name == "nix-path" || name == "extra-nix-path")
|
||||
&& getEnv("NIX_PATH").has_value()) {
|
||||
continue;
|
||||
}
|
||||
set(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Config::resetOverridden()
|
||||
|
|
@ -292,6 +302,7 @@ template<> void BaseSetting<bool>::convertToArg(Args & args, const std::string &
|
|||
{
|
||||
args.addFlag({
|
||||
.longName = name,
|
||||
.aliases = aliases,
|
||||
.description = fmt("Enable the `%s` setting.", name),
|
||||
.category = category,
|
||||
.handler = {[this] { override(true); }},
|
||||
|
|
@ -299,6 +310,7 @@ template<> void BaseSetting<bool>::convertToArg(Args & args, const std::string &
|
|||
});
|
||||
args.addFlag({
|
||||
.longName = "no-" + name,
|
||||
.aliases = aliases,
|
||||
.description = fmt("Disable the `%s` setting.", name),
|
||||
.category = category,
|
||||
.handler = {[this] { override(false); }},
|
||||
|
|
|
|||
|
|
@ -393,7 +393,7 @@ struct ExperimentalFeatureSettings : Config {
|
|||
|
||||
{{#include experimental-features-shortlist.md}}
|
||||
|
||||
Experimental features are [further documented in the manual](@docroot@/contributing/experimental-features.md).
|
||||
Experimental features are [further documented in the manual](@docroot@/development/experimental-features.md).
|
||||
)"};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,13 +15,12 @@
|
|||
|
||||
#if __linux__
|
||||
# include <mutex>
|
||||
# include <sys/resource.h>
|
||||
# include "cgroup.hh"
|
||||
# include "namespaces.hh"
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <sys/mount.h>
|
||||
# include <sys/resource.h>
|
||||
#endif
|
||||
|
||||
namespace nix {
|
||||
|
|
@ -138,7 +137,7 @@ std::optional<Path> getSelfExe()
|
|||
{
|
||||
static auto cached = []() -> std::optional<Path>
|
||||
{
|
||||
#if __linux__
|
||||
#if __linux__ || __GNU__
|
||||
return readLink("/proc/self/exe");
|
||||
#elif __APPLE__
|
||||
char buf[1024];
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#include <algorithm>
|
||||
|
||||
#include "error.hh"
|
||||
#include "environment-variables.hh"
|
||||
#include "signals.hh"
|
||||
|
|
@ -430,4 +432,36 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
|
|||
return out;
|
||||
}
|
||||
|
||||
/** Write to stderr in a robust and minimal way, considering that the process
|
||||
* may be in a bad state.
|
||||
*/
|
||||
static void writeErr(std::string_view buf)
|
||||
{
|
||||
while (!buf.empty()) {
|
||||
auto n = write(STDERR_FILENO, buf.data(), buf.size());
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
abort();
|
||||
}
|
||||
buf = buf.substr(n);
|
||||
}
|
||||
}
|
||||
|
||||
void panic(std::string_view msg)
|
||||
{
|
||||
writeErr("\n\n" ANSI_RED "terminating due to unexpected unrecoverable internal error: " ANSI_NORMAL );
|
||||
writeErr(msg);
|
||||
writeErr("\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
void panic(const char * file, int line, const char * func)
|
||||
{
|
||||
char buf[512];
|
||||
int n = snprintf(buf, sizeof(buf), "Unexpected condition in %s at %s:%d", func, file, line);
|
||||
if (n < 0)
|
||||
panic("Unexpected condition and could not format error message");
|
||||
panic(std::string_view(buf, std::min(static_cast<int>(sizeof(buf)), n)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -273,4 +273,24 @@ using NativeSysError =
|
|||
*/
|
||||
void throwExceptionSelfCheck();
|
||||
|
||||
/**
|
||||
* Print a message and abort().
|
||||
*/
|
||||
[[noreturn]]
|
||||
void panic(std::string_view msg);
|
||||
|
||||
/**
|
||||
* Print a basic error message with source position and abort().
|
||||
* Use the unreachable() macro to call this.
|
||||
*/
|
||||
[[noreturn]]
|
||||
void panic(const char * file, int line, const char * func);
|
||||
|
||||
/**
|
||||
* Print a basic error message with source position and abort().
|
||||
*
|
||||
* @note: This assumes that the logger is operational
|
||||
*/
|
||||
#define unreachable() (::nix::panic(__FILE__, __LINE__, __func__))
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ struct ExperimentalFeatureDetails
|
|||
* feature, we either have no issue at all if few features are not added
|
||||
* at the end of the list, or a proper merge conflict if they are.
|
||||
*/
|
||||
constexpr size_t numXpFeatures = 1 + static_cast<size_t>(Xp::VerifiedFetches);
|
||||
constexpr size_t numXpFeatures = 1 + static_cast<size_t>(Xp::PipeOperators);
|
||||
|
||||
constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails = {{
|
||||
{
|
||||
|
|
@ -287,6 +287,14 @@ constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails
|
|||
)",
|
||||
.trackingUrl = "https://github.com/NixOS/nix/milestone/48",
|
||||
},
|
||||
{
|
||||
.tag = Xp::PipeOperators,
|
||||
.name = "pipe-operators",
|
||||
.description = R"(
|
||||
Add `|>` and `<|` operators to the Nix language.
|
||||
)",
|
||||
.trackingUrl = "https://github.com/NixOS/nix/milestone/55",
|
||||
},
|
||||
}};
|
||||
|
||||
static_assert(
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ enum struct ExperimentalFeature
|
|||
ConfigurableImpureEnv,
|
||||
MountedSSHStore,
|
||||
VerifiedFetches,
|
||||
PipeOperators,
|
||||
};
|
||||
|
||||
extern std::set<std::string> stabilizedFeatures;
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ std::string_view renderFileIngestionMethod(FileIngestionMethod method)
|
|||
case FileIngestionMethod::Git:
|
||||
return "git";
|
||||
default:
|
||||
abort();
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -416,7 +416,11 @@ void deletePath(const fs::path & path)
|
|||
|
||||
void createDir(const Path & path, mode_t mode)
|
||||
{
|
||||
if (mkdir(path.c_str(), mode) == -1)
|
||||
if (mkdir(path.c_str()
|
||||
#ifndef _WIN32
|
||||
, mode
|
||||
#endif
|
||||
) == -1)
|
||||
throw SysError("creating directory '%1%'", path);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ void copyRecursive(
|
|||
throw Error("file '%1%' has an unsupported type", from);
|
||||
|
||||
default:
|
||||
abort();
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ void RestoreSink::createRegularFile(const CanonPath & path, std::function<void(C
|
|||
RestoreRegularFile crf;
|
||||
crf.fd =
|
||||
#ifdef _WIN32
|
||||
CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)
|
||||
CreateFileW(p.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)
|
||||
#else
|
||||
open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666)
|
||||
#endif
|
||||
|
|
@ -145,7 +145,7 @@ void RestoreRegularFile::operator () (std::string_view data)
|
|||
void RestoreSink::createSymlink(const CanonPath & path, const std::string & target)
|
||||
{
|
||||
auto p = append(dstPath, path);
|
||||
nix::createSymlink(target, p);
|
||||
nix::createSymlink(target, p.string());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ std::optional<Mode> convertMode(SourceAccessor::Type type)
|
|||
case SourceAccessor::tRegular: return Mode::Regular;
|
||||
case SourceAccessor::tDirectory: return Mode::Directory;
|
||||
case SourceAccessor::tMisc: return std::nullopt;
|
||||
default: abort();
|
||||
default: unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ static size_t regularHashSize(HashAlgorithm type) {
|
|||
case HashAlgorithm::SHA256: return sha256HashSize;
|
||||
case HashAlgorithm::SHA512: return sha512HashSize;
|
||||
}
|
||||
abort();
|
||||
unreachable();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ struct JSONLogger : Logger {
|
|||
else if (f.type == Logger::Field::tString)
|
||||
arr.push_back(f.s);
|
||||
else
|
||||
abort();
|
||||
unreachable();
|
||||
}
|
||||
|
||||
void write(const nlohmann::json & json)
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ public:
|
|||
return i->second.second;
|
||||
}
|
||||
|
||||
size_t size()
|
||||
size_t size() const
|
||||
{
|
||||
return data.size();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ endif
|
|||
boost = dependency(
|
||||
'boost',
|
||||
modules : ['context', 'coroutine'],
|
||||
include_type: 'system',
|
||||
)
|
||||
# boost is a public dependency, but not a pkg-config dependency unfortunately, so we
|
||||
# put in `deps_other`.
|
||||
|
|
@ -216,6 +217,7 @@ headers = [config_h] + files(
|
|||
'source-accessor.hh',
|
||||
'source-path.hh',
|
||||
'split.hh',
|
||||
'std-hash.hh',
|
||||
'strings.hh',
|
||||
'strings-inline.hh',
|
||||
'suggestions.hh',
|
||||
|
|
|
|||
|
|
@ -88,12 +88,8 @@ mkMesonDerivation (finalAttrs: {
|
|||
LDFLAGS = "-fuse-ld=gold";
|
||||
};
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
separateDebugInfo = !stdenv.hostPlatform.isStatic;
|
||||
|
||||
strictDeps = true;
|
||||
|
||||
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
|
||||
|
||||
meta = {
|
||||
|
|
|
|||
|
|
@ -119,12 +119,12 @@ std::optional<std::string> Pos::getSnippetUpTo(const Pos & end) const {
|
|||
if (auto source = getSource()) {
|
||||
|
||||
auto firstLine = LinesIterator(*source);
|
||||
for (auto i = 1; i < this->line; ++i) {
|
||||
for (uint32_t i = 1; i < this->line; ++i) {
|
||||
++firstLine;
|
||||
}
|
||||
|
||||
auto lastLine = LinesIterator(*source);
|
||||
for (auto i = 1; i < end.line; ++i) {
|
||||
for (uint32_t i = 1; i < end.line; ++i) {
|
||||
++lastLine;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ std::optional<struct stat> PosixSourceAccessor::cachedLstat(const CanonPath & pa
|
|||
Path absPath = makeAbsPath(path).string();
|
||||
|
||||
{
|
||||
auto cache(_cache.read());
|
||||
auto cache(_cache.readLock());
|
||||
auto i = cache->find(absPath);
|
||||
if (i != cache->end()) return i->second;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@ public:
|
|||
: p(r.p)
|
||||
{ }
|
||||
|
||||
explicit ref<T>(const std::shared_ptr<T> & p)
|
||||
explicit ref(const std::shared_ptr<T> & p)
|
||||
: p(p)
|
||||
{
|
||||
if (!p)
|
||||
throw std::invalid_argument("null pointer cast to ref");
|
||||
}
|
||||
|
||||
explicit ref<T>(T * p)
|
||||
explicit ref(T * p)
|
||||
: p(p)
|
||||
{
|
||||
if (!p)
|
||||
|
|
|
|||
|
|
@ -190,11 +190,11 @@ struct VirtualStackAllocator {
|
|||
class DefaultStackAllocator : public StackAllocator {
|
||||
boost::coroutines2::default_stack stack;
|
||||
|
||||
boost::context::stack_context allocate() {
|
||||
boost::context::stack_context allocate() override {
|
||||
return stack.allocate();
|
||||
}
|
||||
|
||||
void deallocate(boost::context::stack_context sctx) {
|
||||
void deallocate(boost::context::stack_context sctx) override {
|
||||
stack.deallocate(sctx);
|
||||
}
|
||||
};
|
||||
|
|
@ -260,7 +260,7 @@ std::unique_ptr<FinishSink> sourceToSink(std::function<void(Source &)> fun)
|
|||
});
|
||||
}
|
||||
|
||||
if (!*coro) { abort(); }
|
||||
if (!*coro) { unreachable(); }
|
||||
|
||||
if (!cur.empty()) {
|
||||
CoroutineContext ctx;
|
||||
|
|
@ -271,12 +271,12 @@ std::unique_ptr<FinishSink> sourceToSink(std::function<void(Source &)> fun)
|
|||
void finish() override
|
||||
{
|
||||
if (!coro) return;
|
||||
if (!*coro) abort();
|
||||
if (!*coro) unreachable();
|
||||
{
|
||||
CoroutineContext ctx;
|
||||
(*coro)(true);
|
||||
}
|
||||
if (*coro) abort();
|
||||
if (*coro) unreachable();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -316,7 +316,7 @@ std::unique_ptr<Source> sinkToSource(
|
|||
});
|
||||
}
|
||||
|
||||
if (!*coro) { eof(); abort(); }
|
||||
if (!*coro) { eof(); unreachable(); }
|
||||
|
||||
if (pos == cur.size()) {
|
||||
if (!cur.empty()) {
|
||||
|
|
|
|||
|
|
@ -159,13 +159,7 @@ struct FdSource : BufferedSource
|
|||
FdSource(Descriptor fd) : fd(fd) { }
|
||||
FdSource(FdSource &&) = default;
|
||||
|
||||
FdSource & operator=(FdSource && s)
|
||||
{
|
||||
fd = s.fd;
|
||||
s.fd = INVALID_DESCRIPTOR;
|
||||
read = s.read;
|
||||
return *this;
|
||||
}
|
||||
FdSource & operator=(FdSource && s) = default;
|
||||
|
||||
bool good() override;
|
||||
protected:
|
||||
|
|
@ -210,7 +204,7 @@ struct TeeSink : Sink
|
|||
{
|
||||
Sink & sink1, & sink2;
|
||||
TeeSink(Sink & sink1, Sink & sink2) : sink1(sink1), sink2(sink2) { }
|
||||
virtual void operator () (std::string_view data)
|
||||
virtual void operator () (std::string_view data) override
|
||||
{
|
||||
sink1(data);
|
||||
sink2(data);
|
||||
|
|
@ -227,7 +221,7 @@ struct TeeSource : Source
|
|||
Sink & sink;
|
||||
TeeSource(Source & orig, Sink & sink)
|
||||
: orig(orig), sink(sink) { }
|
||||
size_t read(char * data, size_t len)
|
||||
size_t read(char * data, size_t len) override
|
||||
{
|
||||
size_t n = orig.read(data, len);
|
||||
sink({data, n});
|
||||
|
|
@ -244,7 +238,7 @@ struct SizedSource : Source
|
|||
size_t remain;
|
||||
SizedSource(Source & orig, size_t size)
|
||||
: orig(orig), remain(size) { }
|
||||
size_t read(char * data, size_t len)
|
||||
size_t read(char * data, size_t len) override
|
||||
{
|
||||
if (this->remain <= 0) {
|
||||
throw EndOfFile("sized: unexpected end-of-file");
|
||||
|
|
@ -489,13 +483,17 @@ struct FramedSource : Source
|
|||
|
||||
~FramedSource()
|
||||
{
|
||||
if (!eof) {
|
||||
while (true) {
|
||||
auto n = readInt(from);
|
||||
if (!n) break;
|
||||
std::vector<char> data(n);
|
||||
from(data.data(), n);
|
||||
try {
|
||||
if (!eof) {
|
||||
while (true) {
|
||||
auto n = readInt(from);
|
||||
if (!n) break;
|
||||
std::vector<char> data(n);
|
||||
from(data.data(), n);
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@
|
|||
#include "ref.hh"
|
||||
#include "canon-path.hh"
|
||||
#include "source-accessor.hh"
|
||||
|
||||
#include <boost/functional/hash.hpp> // for boost::hash_combine
|
||||
#include "std-hash.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
|
|||
24
src/libutil/std-hash.hh
Normal file
24
src/libutil/std-hash.hh
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
//!@file Hashing utilities for use with unordered_map, etc. (ie low level implementation logic, not domain logic like
|
||||
//! Nix hashing)
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* hash_combine() from Boost. Hash several hashable values together
|
||||
* into a single hash.
|
||||
*/
|
||||
inline void hash_combine(std::size_t & seed) {}
|
||||
|
||||
template<typename T, typename... Rest>
|
||||
inline void hash_combine(std::size_t & seed, const T & v, Rest... rest)
|
||||
{
|
||||
std::hash<T> hasher;
|
||||
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||
hash_combine(seed, rest...);
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
|
@ -7,6 +7,8 @@
|
|||
#include <condition_variable>
|
||||
#include <cassert>
|
||||
|
||||
#include "error.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
|
|
@ -47,7 +49,7 @@ public:
|
|||
friend SyncBase;
|
||||
Lock(SyncBase * s) : s(s), lk(s->mutex) { }
|
||||
public:
|
||||
Lock(Lock && l) : s(l.s) { abort(); }
|
||||
Lock(Lock && l) : s(l.s) { unreachable(); }
|
||||
Lock(const Lock & l) = delete;
|
||||
~Lock() { }
|
||||
|
||||
|
|
@ -104,7 +106,7 @@ public:
|
|||
* Acquire read access to the inner value. When using
|
||||
* `std::shared_mutex`, this will use a shared lock.
|
||||
*/
|
||||
ReadLock read() const { return ReadLock(const_cast<SyncBase *>(this)); }
|
||||
ReadLock readLock() const { return ReadLock(const_cast<SyncBase *>(this)); }
|
||||
};
|
||||
|
||||
template<class T>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@ public:
|
|||
#endif
|
||||
;
|
||||
auto count = poll(fds, 1, -1);
|
||||
if (count == -1) abort(); // can't happen
|
||||
if (count == -1)
|
||||
unreachable();
|
||||
|
||||
/* This shouldn't happen, but can on macOS due to a bug.
|
||||
See rdar://37550628.
|
||||
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ static pid_t doFork(bool allowVfork, ChildWrapperFunction & fun)
|
|||
#endif
|
||||
if (pid != 0) return pid;
|
||||
fun();
|
||||
abort();
|
||||
unreachable();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -375,18 +375,4 @@ inline std::string operator + (std::string_view s1, const char * s2)
|
|||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* hash_combine() from Boost. Hash several hashable values together
|
||||
* into a single hash.
|
||||
*/
|
||||
inline void hash_combine(std::size_t & seed) { }
|
||||
|
||||
template <typename T, typename... Rest>
|
||||
inline void hash_combine(std::size_t & seed, const T & v, Rest... rest)
|
||||
{
|
||||
std::hash<T> hasher;
|
||||
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
|
||||
hash_combine(seed, rest...);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue