From 9de5a311722afe9cfeeee5f6e4981556a90e7a49 Mon Sep 17 00:00:00 2001 From: Isaac Khor Date: Wed, 7 Aug 2024 02:40:07 +0000 Subject: [PATCH 01/12] Add initial rpc support --- meson.build | 6 +++ src/bdev_lsvd.cc | 13 +++++- src/bdev_lsvd.h | 1 + src/bdev_lsvd_rpc.cc | 99 ++++++++++++++++++++++++++++++++++++++++++++ src/config.cc | 3 ++ src/config.h | 5 ++- src/lsvd_tgt.cc | 29 +++++++++++++ src/meson.build | 4 ++ src/utils.h | 8 ++++ 9 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 src/bdev_lsvd_rpc.cc create mode 100644 src/lsvd_tgt.cc diff --git a/meson.build b/meson.build index 8455276b..f3948564 100644 --- a/meson.build +++ b/meson.build @@ -30,6 +30,12 @@ executable( dependencies: lsvd_deps + [dependency('_spdk')], ) +executable( + 'lsvd_tgt', + lsvd_tgt, + dependencies: lsvd_deps + [dependency('_spdk')], +) + executable( 'imgtool', ['src/imgtool.cc'], diff --git a/src/bdev_lsvd.cc b/src/bdev_lsvd.cc index 8890edc6..769ac1ba 100644 --- a/src/bdev_lsvd.cc +++ b/src/bdev_lsvd.cc @@ -7,6 +7,8 @@ #include "request.h" #include "smartiov.h" #include "spdk/thread.h" +#include "src/backend.h" +#include "src/config.h" #include "utils.h" static int bdev_lsvd_init(void); @@ -217,7 +219,16 @@ int bdev_lsvd_create(str img_name, rados_ioctx_t ioctx, lsvd_config cfg) return 0; } -int bdev_lsvd_delete(std::string img_name) +int bdev_lsvd_create(str pool_name, str image_name, str cfg_path) +{ + auto be = connect_to_pool(pool_name); + auto cfg = lsvd_config::from_file(cfg_path); + PR_RET_IF(!cfg.has_value(), -1, "Failed to read config file"); + + return bdev_lsvd_create(image_name, be, cfg.value()); +} + +int bdev_lsvd_delete(str img_name) { auto p = std::promise(); spdk_bdev_unregister_by_name( diff --git a/src/bdev_lsvd.h b/src/bdev_lsvd.h index 294298ce..d372a63c 100644 --- a/src/bdev_lsvd.h +++ b/src/bdev_lsvd.h @@ -5,4 +5,5 @@ #include "config.h" int bdev_lsvd_create(str img_name, rados_ioctx_t io_ctx, lsvd_config cfg); +int bdev_lsvd_create(str pool_name, str image_name, str cfg_path); int bdev_lsvd_delete(str img_name); diff --git a/src/bdev_lsvd_rpc.cc b/src/bdev_lsvd_rpc.cc new file mode 100644 index 00000000..067ed163 --- /dev/null +++ b/src/bdev_lsvd_rpc.cc @@ -0,0 +1,99 @@ +#include "spdk/bdev.h" +#include "spdk/json.h" +#include "spdk/jsonrpc.h" +#include "spdk/likely.h" +#include "spdk/log.h" +#include "spdk/nvme.h" +#include "spdk/rpc.h" +#include "spdk/util.h" + +#include "bdev_lsvd.h" +#include "utils.h" + +/** + * We only expose 2 RPC endpoints: create and delete. Unlike RBD, we will not + * have commands to manage ceph clusters; each image will create its own. + */ + +struct rpc_create_lsvd { + char *image_name; + char *pool_name; + char *config; +}; + +static const struct spdk_json_object_decoder rpc_create_lsvd_decoders[] = { + {"image_name", offsetof(rpc_create_lsvd, image_name), + spdk_json_decode_string, false}, + {"pool_name", offsetof(rpc_create_lsvd, pool_name), spdk_json_decode_string, + false}, + {"config", offsetof(rpc_create_lsvd, config), spdk_json_decode_string, + true}, +}; + +static void rpc_bdev_lsvd_create(spdk_jsonrpc_request *req_json, + const spdk_json_val *params) +{ + spdk_json_write_ctx *w; + std::unique_ptrimage_name); + free(p->pool_name); + free(p->config); + })> + req(new rpc_create_lsvd()); + + auto rc = spdk_json_decode_object(params, rpc_create_lsvd_decoders, + SPDK_COUNTOF(rpc_create_lsvd_decoders), + req.get()); + PR_GOTO_IF(rc != 0, fail, "spdk_json_decode_object failed\n"); + + rc = bdev_lsvd_create(req->pool_name, req->image_name, req->config); + PR_GOTO_IF(rc != 0, fail, "failed to create lsvd bdev\n"); + + w = spdk_jsonrpc_begin_result(req_json); + PR_GOTO_IF(w == nullptr, fail, "failed to create json result\n"); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(req_json, w); + +fail: + spdk_jsonrpc_send_error_response(req_json, rc, + "failed to create lsvd bdev"); +} + +SPDK_RPC_REGISTER("bdev_lsvd_create", rpc_bdev_lsvd_create, SPDK_RPC_RUNTIME) + +struct rpc_delete_lsvd { + char *image_name; +}; + +static const struct spdk_json_object_decoder rpc_delete_lsvd_decoders[] = { + {"image_name", offsetof(struct rpc_delete_lsvd, image_name), + spdk_json_decode_string, false}, +}; + +static void rpc_bdev_lsvd_delete(struct spdk_jsonrpc_request *req_json, + const struct spdk_json_val *params) +{ + spdk_json_write_ctx *w; + std::unique_ptrimage_name); })> + req(new rpc_delete_lsvd()); + + int rc = spdk_json_decode_object(params, rpc_delete_lsvd_decoders, + SPDK_COUNTOF(rpc_delete_lsvd_decoders), + req.get()); + PR_GOTO_IF(rc != 0, fail, "failed to decode json object\n"); + + rc = bdev_lsvd_delete(req->image_name); + PR_GOTO_IF(rc != 0, fail, "failed to destroy lsvd bdev"); + + w = spdk_jsonrpc_begin_result(req_json); + PR_GOTO_IF(w == nullptr, fail, "failed to create json result\n"); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(req_json, w); + +fail: + spdk_jsonrpc_send_error_response(req_json, rc, + "failed to create lsvd bdev"); +} + +SPDK_RPC_REGISTER("bdev_lsvd_delete", rpc_bdev_lsvd_delete, SPDK_RPC_RUNTIME) diff --git a/src/config.cc b/src/config.cc index 04dfde18..a8e80fce 100644 --- a/src/config.cc +++ b/src/config.cc @@ -9,6 +9,7 @@ * LGPL-2.1-or-later */ +#include "src/utils.h" #include #include #include @@ -112,6 +113,8 @@ int lsvd_config::read() return 0; // success } +opt lsvd_config::from_file(str path) { UNIMPLEMENTED(); } + std::string lsvd_config::cache_filename(uuid_t &uuid, const char *name, cfg_cache_type type) { diff --git a/src/config.h b/src/config.h index 446f64cf..b207dd72 100644 --- a/src/config.h +++ b/src/config.h @@ -45,12 +45,13 @@ class lsvd_config lsvd_config() {} ~lsvd_config() {} int read(); - std::string cache_filename(uuid_t &uuid, const char *name, - cfg_cache_type type); + str cache_filename(uuid_t &uuid, const char *name, cfg_cache_type type); inline fspath wlog_path(str imgname) { auto filename = imgname + ".wlog"; return fspath(wcache_dir) / filename; } + + static opt from_file(str path); }; diff --git a/src/lsvd_tgt.cc b/src/lsvd_tgt.cc new file mode 100644 index 00000000..aa7dfb4b --- /dev/null +++ b/src/lsvd_tgt.cc @@ -0,0 +1,29 @@ +#include "spdk/event.h" + +#include "utils.h" + +static void lsvd_tgt_usage() {} +static int lsvd_tgt_parse_arg(int ch, char *arg) { return 0; } + +static void lsvd_tgt_started(void *arg1) +{ + log_info("LSVD SPDK nvmf target started"); +} + +int main(int argc, char **argv) +{ + int rc; + struct spdk_app_opts opts = {}; + + spdk_app_opts_init(&opts, sizeof(opts)); + opts.name = "lsvd_tgt"; + if ((rc = spdk_app_parse_args(argc, argv, &opts, "", NULL, + lsvd_tgt_parse_arg, lsvd_tgt_usage)) != + SPDK_APP_PARSE_ARGS_SUCCESS) { + exit(rc); + } + + rc = spdk_app_start(&opts, lsvd_tgt_started, NULL); + spdk_app_fini(); + return rc; +} diff --git a/src/meson.build b/src/meson.build index 5af34b55..7d8ac40e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -36,4 +36,8 @@ lsvd_deps = [ spdk_fe = lsvd_src + files( 'bdev_lsvd.cc', 'spdk_frontend.cc', +) + +lsvd_tgt = lsvd_src + files( + 'lsvd_tgt.cc', ) \ No newline at end of file diff --git a/src/utils.h b/src/utils.h index 66733c59..63d9daa9 100644 --- a/src/utils.h +++ b/src/utils.h @@ -124,6 +124,14 @@ using fspath = std::filesystem::path; } \ } while (0) +#define PR_GOTO_IF(cond, lbl, MSG, ...) \ + do { \ + if (cond) { \ + log_error(MSG, ##__VA_ARGS__); \ + goto lbl; \ + } \ + } while (0) + /** * If `cond` is true, print an error message to stdout with MSG, then return * `ret` From 9293eb91898f003f24510dc893d88e29154a7a26 Mon Sep 17 00:00:00 2001 From: Isaac Khor Date: Thu, 8 Aug 2024 00:58:42 +0000 Subject: [PATCH 02/12] Add rpc plugin --- src/meson.build | 2 ++ src/rpc_plugin.py | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/rpc_plugin.py diff --git a/src/meson.build b/src/meson.build index 7d8ac40e..b08ba529 100644 --- a/src/meson.build +++ b/src/meson.build @@ -40,4 +40,6 @@ spdk_fe = lsvd_src + files( lsvd_tgt = lsvd_src + files( 'lsvd_tgt.cc', + 'bdev_lsvd.cc', + 'bdev_lsvd_rpc.cc', ) \ No newline at end of file diff --git a/src/rpc_plugin.py b/src/rpc_plugin.py new file mode 100644 index 00000000..1d814d0a --- /dev/null +++ b/src/rpc_plugin.py @@ -0,0 +1,27 @@ +from spdk.rpc.client import print_json + +def spdk_rpc_plugin_initialize(subparsers): + def bdev_lsvd_create(args): + print_json(args.client.call('bdev_lsvd_create', { + 'name': args.name, + 'pool_name': args.pool_name, + 'cfg_path': args.cfg + })) + + p = subparsers.add_parser('bdev_lsvd_create', help='Create a bdev with LSVD backend') + p.add_argument('pool_name', help='Name of the ceph pool') + p.add_argument('name', help='Name of the lsvd disk image') + p.add_argument('-c', '--cfg', help='Path to config file', required=False) + p.set_defaults(func=bdev_lsvd_create) + + def bdev_lsvd_delete(args): + print_json(args.client.call('bdev_lsvd_delete', { + 'name': args.name + })) + + p = subparsers.add_parser('bdev_lsvd_delete', help='Delete a lsvd bdev') + p.add_argument('name', help='Name of the lsvd disk image') + p.set_defaults(func=bdev_lsvd_delete) + + + From 6bf5b280f173540f89ec3a82076b43b8e72cee44 Mon Sep 17 00:00:00 2001 From: Isaac Khor Date: Thu, 8 Aug 2024 02:26:47 +0000 Subject: [PATCH 03/12] Add initial config file parsing --- .gitignore | 1 + docs/configuration.md | 14 +++ docs/example_config.json | 18 ++++ src/config.cc | 204 ++++++++++++++------------------------- src/config.h | 63 ++++++------ src/image.cc | 6 +- src/meson.build | 1 + src/rpc_plugin.py | 2 +- src/spdk_frontend.cc | 2 +- src/translate.cc | 26 ++--- src/write_cache.cc | 4 +- 11 files changed, 152 insertions(+), 189 deletions(-) create mode 100644 docs/configuration.md create mode 100644 docs/example_config.json diff --git a/.gitignore b/.gitignore index 8b84efbf..8be2b408 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ qemu/bzImage subprojects/liburing-* subprojects/packagecache +subprojects/nlohmann_json* diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..4b3c2a9d --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,14 @@ +# Configuring LSVD + +LSVD is configured using a JSON file. When creating an image, we will +try to read the following paths and parse them for configuration options: + +- Default built-in configuration +- `/usr/local/etc/lsvd.json` +- `./lsvd.json` +- user supplied path + +The file read last has highest priority. + +We will also first try to parse the user-supplied path as a JSON object, and if +that fails try treat it as a path and read it from a file. diff --git a/docs/example_config.json b/docs/example_config.json new file mode 100644 index 00000000..653a3e0f --- /dev/null +++ b/docs/example_config.json @@ -0,0 +1,18 @@ +{ + "rcache_dir": "/tmp/lsvd", + "rcache_size": 524288000, + "rcache_fetch_window": 12, + "wlog_dir": "/tmp/lsvd", + "wlog_bytes": 524288000, + "wlog_write_window": 8, + "wlog_chunk_bytes": 2097152, + "antithrash_ratio": 67, + "backend_obj_bytes": 8388608, + "backend_write_window": 8, + "checkpoint_interval_objs": 500, + "flush_timeout_ms": 2000, + "flush_interval_ms": 1000, + "gc_threshold_pc": 60, + "gc_write_window": 4, + "no_gc": false +} \ No newline at end of file diff --git a/src/config.cc b/src/config.cc index a8e80fce..06bdedd5 100644 --- a/src/config.cc +++ b/src/config.cc @@ -1,153 +1,91 @@ -/* - * file: config.cc - * description: quick and dirty config file parser - * env var overrides modeled on github.com/spf13/viper - * - * author: Peter Desnoyers, Northeastern University - * Copyright 2021, 2022 Peter Desnoyers - * license: GNU LGPL v2.1 or newer - * LGPL-2.1-or-later - */ - -#include "src/utils.h" -#include +#include #include +#include #include #include -#include -#include -#include -#include -#include -namespace fs = std::filesystem; - #include "config.h" -#include "config_macros.h" +#include "utils.h" vec cfg_path({"lsvd.conf", "/usr/local/etc/lsvd.conf"}); -static void split(std::string s, vec &words) -{ - std::string w = ""; - for (auto c : s) { - if (!isspace(c)) - w = w + c; - else { - words.push_back(w); - w = ""; - } - } - if (w.size() > 0) - words.push_back(w); -} - -static std::map m = {{"file", BACKEND_FILE}, - {"rados", BACKEND_RADOS}}; - -/* fancy-ass macros to parse config file lines. - * config keyword = field name - * environment var = LSVD_ + uppercase(field name) - * skips blank lines and lines that don't match a keyword - */ - -int lsvd_config::read() +opt lsvd_config::from_file(str path) { - auto explicit_cfg = getenv("LSVD_CONFIG_FILE"); - if (explicit_cfg) { - std::string f(explicit_cfg); - cfg_path.insert(cfg_path.begin(), f); - } - for (auto f : cfg_path) { - std::ifstream fp(f); - if (!fp.is_open()) - continue; - std::string line; - while (getline(fp, line)) { - if (line[0] == '#') - continue; - vec words; - split(line, words); - if (words.size() != 2) - continue; - F_CONFIG_H_INT(words[0], words[1], backend_obj_size); - F_CONFIG_INT(words[0], words[1], wcache_batch); - F_CONFIG_H_INT(words[0], words[1], wcache_chunk); - F_CONFIG_STR(words[0], words[1], rcache_dir); - F_CONFIG_STR(words[0], words[1], wcache_dir); - F_CONFIG_INT(words[0], words[1], num_parallel_writes); - F_CONFIG_TABLE(words[0], words[1], backend, m); - F_CONFIG_H_INT(words[0], words[1], cache_size); - F_CONFIG_H_INT(words[0], words[1], wlog_size); - F_CONFIG_INT(words[0], words[1], hard_sync); - F_CONFIG_INT(words[0], words[1], ckpt_interval); - F_CONFIG_INT(words[0], words[1], flush_timeout_msec); - F_CONFIG_INT(words[0], words[1], gc_threshold); - F_CONFIG_INT(words[0], words[1], fetch_window); - F_CONFIG_INT(words[0], words[1], fetch_ratio); - F_CONFIG_INT(words[0], words[1], no_gc); - F_CONFIG_INT(words[0], words[1], gc_window); - } - fp.close(); - break; - } + // write just a simple braindead parser + // syntax: lines of `key = value` - ENV_CONFIG_H_INT(backend_obj_size); - ENV_CONFIG_INT(wcache_batch); - ENV_CONFIG_H_INT(wcache_chunk); - ENV_CONFIG_STR(rcache_dir); - ENV_CONFIG_STR(wcache_dir); - ENV_CONFIG_INT(num_parallel_writes); - ENV_CONFIG_TABLE(backend, m); - ENV_CONFIG_H_INT(cache_size); - ENV_CONFIG_H_INT(wlog_size); - ENV_CONFIG_INT(hard_sync); - ENV_CONFIG_INT(ckpt_interval); - ENV_CONFIG_INT(flush_timeout_msec); - ENV_CONFIG_INT(gc_threshold); - ENV_CONFIG_INT(fetch_window); - ENV_CONFIG_INT(fetch_ratio); - ENV_CONFIG_INT(no_gc); - ENV_CONFIG_INT(gc_window); - - return 0; // success + UNIMPLEMENTED(); } -opt lsvd_config::from_file(str path) { UNIMPLEMENTED(); } - -std::string lsvd_config::cache_filename(uuid_t &uuid, const char *name, - cfg_cache_type type) +lsvd_config lsvd_config::get_default() { return lsvd_config(); } + +#define JSON_GET_BOOL(key) \ + do { \ + if (js.contains(#key)) { \ + auto v = js[#key]; \ + if (v.is_boolean()) { \ + cfg.key = js[#key]; \ + } else { \ + log_error("Invalid value for key (must be bool): {}", #key); \ + } \ + } \ + } while (0) + +#define JSON_GET_UINT(key) \ + do { \ + if (js.contains(#key)) { \ + auto v = js[#key]; \ + if (v.is_number_unsigned()) { \ + cfg.key = js[#key]; \ + } else { \ + log_error("Invalid value for key (must be uint): {}", #key); \ + } \ + } \ + } while (0) + +#define JSON_GET_STR(key) \ + do { \ + if (js.contains(#key)) { \ + auto v = js[#key]; \ + if (v.is_string()) { \ + cfg.key = js[#key]; \ + } else { \ + log_error("Invalid value for key (must be str): {}", #key); \ + } \ + } \ + } while (0) + +opt lsvd_config::from_json(str json) { - char buf[256]; // PATH_MAX - std::string file(name); - file = fs::path(file).filename(); - const char *dir; - const char *f_ext; + nlohmann::json js; - dir = (type == LSVD_CFG_READ) ? rcache_dir.c_str() : wcache_dir.c_str(); - f_ext = (type == LSVD_CFG_READ) ? "rcache" : "wcache"; + try { + js = nlohmann::json::parse(json); + } catch (nlohmann::json::parse_error &e) { + log_error("Failed to parse JSON: {}", e.what()); + return std::nullopt; + } - sprintf(buf, "%s/%s.%s", dir, file.c_str(), f_ext); - if (access(buf, R_OK | W_OK) == 0) - return std::string((const char *)buf); + lsvd_config cfg; + JSON_GET_STR(rcache_dir); + JSON_GET_UINT(rcache_bytes); + JSON_GET_UINT(rcache_fetch_window); - char uuid_s[64]; - uuid_unparse(uuid, uuid_s); - sprintf(buf, "%s/%s.%s", dir, uuid_s, f_ext); - return std::string((const char *)buf); -} + JSON_GET_STR(wlog_dir); + JSON_GET_UINT(wlog_bytes); + JSON_GET_UINT(wlog_write_window); + JSON_GET_UINT(wlog_chunk_bytes); -#if 0 -int main(int argc, char **argv) { - auto cfg = new lsvd_config; - cfg->read(); + JSON_GET_UINT(antithrash_ratio); + JSON_GET_UINT(backend_obj_bytes); + JSON_GET_UINT(backend_write_window); + JSON_GET_UINT(checkpoint_interval_objs); + JSON_GET_UINT(flush_timeout_ms); + JSON_GET_UINT(flush_interval_ms); - printf("batch: %d\n", cfg->batch_size); // h_int - printf("wc batch %d\n", cfg->wcache_batch); // int - printf("cache: %s\n", cfg->cache_dir.c_str()); // str - printf("backend: %d\n", (int)cfg->backend); // table + JSON_GET_UINT(gc_threshold_pc); + JSON_GET_UINT(gc_write_window); + JSON_GET_BOOL(no_gc); - uuid_t uu; - std::cout << cfg->cache_filename(uu, "foobar") << "\n"; + return cfg; } -#endif diff --git a/src/config.h b/src/config.h index b207dd72..857b2bc0 100644 --- a/src/config.h +++ b/src/config.h @@ -1,57 +1,48 @@ #pragma once -/* - * file: config.h - * description: quick and dirty config file parser - * env var overrides modeled on github.com/spf13/viper - * - * author: Peter Desnoyers, Northeastern University - * Copyright 2021, 2022 Peter Desnoyers - * license: GNU LGPL v2.1 or newer - * LGPL-2.1-or-later - */ - -#include #include #include "utils.h" -enum cfg_backend { BACKEND_FILE = 1, BACKEND_RADOS = 2 }; - -enum cfg_cache_type { LSVD_CFG_READ = 1, LSVD_CFG_WRITE = 2 }; - class lsvd_config { public: - int backend_obj_size = 8 * 1024 * 1024; // in bytes - int wcache_batch = 8; // requests - int wcache_chunk = 2 * 1024 * 1024; // bytes - std::string rcache_dir = "/tmp/lsvd/"; - std::string wcache_dir = "/tmp/lsvd/"; - u32 num_parallel_writes = 8; - int hard_sync = 0; - enum cfg_backend backend = BACKEND_RADOS; - long cache_size = 500 * 1024 * 1024; // in bytes - long wlog_size = 500 * 1024 * 1024; // in bytes - int ckpt_interval = 500; // objects - int flush_timeout_msec = 2000; // flush timeout - int flush_interval_msec = 1000; // flush interval - int gc_threshold = 60; // GC threshold, percent - int gc_window = 4; // max GC writes outstanding - int fetch_window = 12; // read cache fetches - int fetch_ratio = 67; // anti-thrash served:backend ratio - int no_gc = 0; // turn off GC + // read cache + str rcache_dir = "/tmp/lsvd/"; // directory to store read cache files + u64 rcache_bytes = 500 * 1024 * 1024; // in bytes + u64 rcache_fetch_window = 12; // read cache fetches + + // write log + str wlog_dir = "/tmp/lsvd/"; + u64 wlog_bytes = 500 * 1024 * 1024; // in bytes + u64 wlog_write_window = 8; // requests + u64 wlog_chunk_bytes = 2 * 1024 * 1024; // bytes + + // backend + u64 antithrash_ratio = 67; // anti-thrash served:backend ratio + u64 backend_obj_bytes = 8 * 1024 * 1024; // in bytes + u64 backend_write_window = 8; // backend parallel writes + + u64 checkpoint_interval_objs = 500; // objects + u64 flush_timeout_ms = 2000; // flush timeout + u64 flush_interval_ms = 1000; // flush interval + + // GC + u64 gc_threshold_pc = 60; // GC threshold, percent + u64 gc_write_window = 4; // max GC writes outstanding + bool no_gc = false; // turn off GC lsvd_config() {} ~lsvd_config() {} int read(); - str cache_filename(uuid_t &uuid, const char *name, cfg_cache_type type); inline fspath wlog_path(str imgname) { auto filename = imgname + ".wlog"; - return fspath(wcache_dir) / filename; + return fspath(wlog_dir) / filename; } + static opt from_json(str json); static opt from_file(str path); + static lsvd_config get_default(); }; diff --git a/src/image.cc b/src/image.cc index 0db401ab..9eab7bea 100644 --- a/src/image.cc +++ b/src/image.cc @@ -19,7 +19,7 @@ lsvd_image::lsvd_image(std::string name, rados_ioctx_t io, lsvd_config cfg_) : imgname(name), cfg(cfg_), io(io) { objstore = make_rados_backend(io); - rcache = get_read_cache_instance(cfg.rcache_dir, cfg.cache_size, objstore); + rcache = get_read_cache_instance(cfg.rcache_dir, cfg.rcache_size, objstore); read_superblock(); debug("Found checkpoints: {}", checkpoints); @@ -165,7 +165,7 @@ seqnum_t lsvd_image::roll_forward_from_last_checkpoint() // This must be larger than the max backend batch size to avoid // potential corruption if subsequent breaks overlap with current dangling // objects and we get writes from two different "generations" - for (seqnum_t i = 1; i < cfg.num_parallel_writes * 4; i++) + for (seqnum_t i = 1; i < cfg.backend_write_window * 4; i++) objstore->delete_obj(oname(imgname, seq + i)); return seq; @@ -478,7 +478,7 @@ class lsvd_image::write_request : public lsvd_image::aio_request sector_t size_sectors = req_bytes / 512; // split large requests into 2MB (default) chunks - sector_t max_sectors = img->cfg.wcache_chunk / 512; + sector_t max_sectors = img->cfg.wlog_chunk_bytes / 512; n_req += div_round_up(req_bytes / 512, max_sectors); // TODO: this is horribly ugly diff --git a/src/meson.build b/src/meson.build index b08ba529..d43a21d3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -29,6 +29,7 @@ lsvd_deps = [ dependency('boost', modules: ['system', 'filesystem', 'program_options', 'thread', 'regex']), dependency('liburing', static: true), dependency('uuid'), + dependency('nlohmann_json'), cxx.find_library('rados', required: true), cxx.find_library('jemalloc', required: false), ] diff --git a/src/rpc_plugin.py b/src/rpc_plugin.py index 1d814d0a..3a852f9d 100644 --- a/src/rpc_plugin.py +++ b/src/rpc_plugin.py @@ -11,7 +11,7 @@ def bdev_lsvd_create(args): p = subparsers.add_parser('bdev_lsvd_create', help='Create a bdev with LSVD backend') p.add_argument('pool_name', help='Name of the ceph pool') p.add_argument('name', help='Name of the lsvd disk image') - p.add_argument('-c', '--cfg', help='Path to config file', required=False) + p.add_argument('-c', '--cfg', help='Path to config file OR inline JSON string', required=False) p.set_defaults(func=bdev_lsvd_create) def bdev_lsvd_delete(args): diff --git a/src/spdk_frontend.cc b/src/spdk_frontend.cc index 2b3034c6..75b9cd08 100644 --- a/src/spdk_frontend.cc +++ b/src/spdk_frontend.cc @@ -182,7 +182,7 @@ static void start_lsvd(void *arg) // Add lsvd bdev lsvd_config cfg; // TODO read this in from a config file - cfg.cache_size = 160 * 1024 * 1024; // small 160mb cache for testing + cfg.rcache_bytes = 160 * 1024 * 1024; // small 160mb cache for testing auto err = bdev_lsvd_create(args->image_name, io_ctx, cfg); assert(err == 0); add_bdev_ns(nvme_ss, args->image_name); diff --git a/src/translate.cc b/src/translate.cc index 0806980c..0797ae86 100644 --- a/src/translate.cc +++ b/src/translate.cc @@ -237,11 +237,11 @@ class translate_impl : public translate total_live_sectors += oi.live; } - current = new translate_req(REQ_PUT, cfg.backend_obj_size, this); + current = new translate_req(REQ_PUT, cfg.backend_obj_bytes, this); assert(current->batch_buf != nullptr); // start worker, flush, and GC threads - if (cfg.flush_interval_msec > 0) + if (cfg.flush_interval_ms > 0) flush_worker = std::jthread([this](std::stop_token st) { flush_thread(st); }); @@ -400,7 +400,7 @@ ssize_t translate_impl::writev(uint64_t cache_seq, size_t offset, iovec *iov, std::unique_lock lk(m); if (!current->room(bytes)) { workers->put_locked(current); - current = new translate_req(REQ_PUT, cfg.backend_obj_size, this); + current = new translate_req(REQ_PUT, cfg.backend_obj_bytes, this); } // write the data into the in-memory log @@ -459,7 +459,7 @@ ssize_t translate_impl::trim(size_t offset, size_t len) void translate_impl::backend_backpressure(void) { std::unique_lock lk(m); - while (outstanding_writes > cfg.num_parallel_writes) + while (outstanding_writes > cfg.backend_write_window) cv.wait(lk); } @@ -643,7 +643,7 @@ void translate_impl::write_gc(seqnum_t _seq, translate_req *req) data_sectors += e.len; int max_hdr_bytes = sizeof(common_obj_hdr) + sizeof(obj_data_hdr) + - (cfg.backend_obj_size / 2048) * sizeof(data_map); + (cfg.backend_obj_bytes / 2048) * sizeof(data_map); int max_hdr_sectors = div_round_up(max_hdr_bytes, 512); auto buf = req->gc_buf = @@ -855,7 +855,7 @@ void translate_impl::flush(void) if (current->len > 0) { workers->put_locked(current); - current = new translate_req(REQ_PUT, cfg.backend_obj_size, this); + current = new translate_req(REQ_PUT, cfg.backend_obj_bytes, this); } auto flush_req = new translate_req(REQ_FLUSH, this); @@ -870,7 +870,7 @@ void translate_impl::checkpoint(void) if (current->len > 0) { workers->put_locked(current); - current = new translate_req(REQ_PUT, cfg.backend_obj_size, this); + current = new translate_req(REQ_PUT, cfg.backend_obj_bytes, this); } auto ckpt_req = new translate_req(REQ_CKPT, this); @@ -886,8 +886,8 @@ void translate_impl::checkpoint(void) void translate_impl::flush_thread(std::stop_token st) { pthread_setname_np(pthread_self(), "flush_thread"); - auto interval = std::chrono::milliseconds(cfg.flush_interval_msec); - auto timeout = std::chrono::milliseconds(cfg.flush_timeout_msec); + auto interval = std::chrono::milliseconds(cfg.flush_interval_ms); + auto timeout = std::chrono::milliseconds(cfg.flush_timeout_ms); auto t0 = std::chrono::system_clock::now(); auto seq0 = cur_seq.load(); @@ -902,7 +902,7 @@ void translate_impl::flush_thread(std::stop_token st) if (std::chrono::system_clock::now() - t0 < timeout) continue; workers->put_locked(current); - current = new translate_req(REQ_PUT, cfg.backend_obj_size, this); + current = new translate_req(REQ_PUT, cfg.backend_obj_bytes, this); } else { seq0 = cur_seq.load(); t0 = std::chrono::system_clock::now(); @@ -993,7 +993,7 @@ void translate_impl::do_gc(std::stop_token &st) /* gather list of objects needing cleaning, return if none */ - const double threshold = cfg.gc_threshold / 100.0; + const double threshold = cfg.gc_threshold_pc / 100.0; vec> objs_to_clean; for (auto [u, o, n] : utilization) { if (u > threshold) @@ -1076,7 +1076,7 @@ void translate_impl::do_gc(std::stop_token &st) std::queue requests; while (all_extents.size() > 0) { - sector_t sectors = 0, max = cfg.backend_obj_size / 512; + sector_t sectors = 0, max = cfg.backend_obj_bytes / 512; vec<_extent> extents; auto it = all_extents.begin(); @@ -1111,7 +1111,7 @@ void translate_impl::do_gc(std::stop_token &st) workers->put_locked(req); lk.unlock(); - while ((int)requests.size() > cfg.gc_window && + while ((int)requests.size() > cfg.gc_write_window && !st.stop_requested()) { auto t = requests.front(); t->wait(); diff --git a/src/write_cache.cc b/src/write_cache.cc index a4d22aa0..ffcceca7 100644 --- a/src/write_cache.cc +++ b/src/write_cache.cc @@ -474,7 +474,7 @@ write_cache_impl::write_cache_impl(int fd, translate &be, lsvd_config &cfg) int n_pages = super->limit - super->base; max_write_pages = n_pages / 2 + n_pages / 4; // no idea why this is 3/4ths - write_batch = cfg.wcache_batch; + write_batch = cfg.wlog_write_window; } write_cache_impl::~write_cache_impl() @@ -580,7 +580,7 @@ uptr open_wlog(fspath path, translate &xlate, lsvd_config &cfg) fd = open(path.c_str(), O_RDWR | O_CREAT, 0644); PR_ERR_RET_IF(fd < 0, nullptr, errno, "Failed to create cache file"); - auto err = init_wcache(fd, xlate.uuid, cfg.wlog_size); + auto err = init_wcache(fd, xlate.uuid, cfg.wlog_bytes); PR_ERR_RET_IF(err < 0, nullptr, -err, "Failed to init wlog"); } else { fd = open(path.c_str(), O_RDWR); From 2f7eca9fa88c4c458696152c70102fb8b6ec928a Mon Sep 17 00:00:00 2001 From: Isaac Khor Date: Thu, 8 Aug 2024 02:29:43 +0000 Subject: [PATCH 04/12] Clean up old uses of previous config reader --- src/bdev_lsvd.cc | 4 ++-- src/config.h | 5 +++-- src/spdk_wrap.cc | 7 ++----- src/translate.cc | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/bdev_lsvd.cc b/src/bdev_lsvd.cc index 769ac1ba..d90641ac 100644 --- a/src/bdev_lsvd.cc +++ b/src/bdev_lsvd.cc @@ -2,13 +2,13 @@ #include "spdk/bdev_module.h" #include +#include "backend.h" #include "bdev_lsvd.h" +#include "config.h" #include "image.h" #include "request.h" #include "smartiov.h" #include "spdk/thread.h" -#include "src/backend.h" -#include "src/config.h" #include "utils.h" static int bdev_lsvd_init(void); diff --git a/src/config.h b/src/config.h index 857b2bc0..e7c602cf 100644 --- a/src/config.h +++ b/src/config.h @@ -32,9 +32,7 @@ class lsvd_config u64 gc_write_window = 4; // max GC writes outstanding bool no_gc = false; // turn off GC - lsvd_config() {} ~lsvd_config() {} - int read(); inline fspath wlog_path(str imgname) { @@ -45,4 +43,7 @@ class lsvd_config static opt from_json(str json); static opt from_file(str path); static lsvd_config get_default(); + + private: + lsvd_config() {} }; diff --git a/src/spdk_wrap.cc b/src/spdk_wrap.cc index d7d90941..71621f90 100644 --- a/src/spdk_wrap.cc +++ b/src/spdk_wrap.cc @@ -1,6 +1,6 @@ #include "spdk_wrap.h" #include "config.h" -#include "src/utils.h" +#include "utils.h" spdk_completion::spdk_completion(rbd_callback_t cb, void *cb_arg) : cb(cb), cb_arg(cb_arg) @@ -59,10 +59,7 @@ inline void spdk_completion::dec_and_free() lsvd_rbd *lsvd_rbd::open_image(rados_ioctx_t io, std::string name) { try { - lsvd_config cfg; - auto err = cfg.read(); - PR_ERR_RET_IF(err < 0, nullptr, -err, "Failed to read config"); - + auto cfg = lsvd_config::get_default(); return new lsvd_rbd(name, io, cfg); } catch (std::runtime_error &e) { log_error("Failed to open image: {}", e.what()); diff --git a/src/translate.cc b/src/translate.cc index 0797ae86..76a3fd90 100644 --- a/src/translate.cc +++ b/src/translate.cc @@ -13,8 +13,8 @@ #include "extent.h" #include "misc_cache.h" #include "request.h" -#include "src/utils.h" #include "translate.h" +#include "utils.h" /* * Architecture: From 234981ebb46e2027d6dde677d88b7aa4762cd6ca Mon Sep 17 00:00:00 2001 From: Isaac Khor Date: Thu, 8 Aug 2024 02:50:47 +0000 Subject: [PATCH 05/12] Switch over to new user cfg system --- src/bdev_lsvd.cc | 4 +-- src/config.cc | 80 ++++++++++++++++++++++++++++++++------------ src/config.h | 11 ++++-- src/image.cc | 3 +- src/spdk_frontend.cc | 5 ++- src/translate.cc | 2 +- 6 files changed, 75 insertions(+), 30 deletions(-) diff --git a/src/bdev_lsvd.cc b/src/bdev_lsvd.cc index d90641ac..734ff5b8 100644 --- a/src/bdev_lsvd.cc +++ b/src/bdev_lsvd.cc @@ -219,10 +219,10 @@ int bdev_lsvd_create(str img_name, rados_ioctx_t ioctx, lsvd_config cfg) return 0; } -int bdev_lsvd_create(str pool_name, str image_name, str cfg_path) +int bdev_lsvd_create(str pool_name, str image_name, str user_cfg) { auto be = connect_to_pool(pool_name); - auto cfg = lsvd_config::from_file(cfg_path); + auto cfg = lsvd_config::from_user_cfg(user_cfg); PR_RET_IF(!cfg.has_value(), -1, "Failed to read config file"); return bdev_lsvd_create(image_name, be, cfg.value()); diff --git a/src/config.cc b/src/config.cc index 06bdedd5..8e8cac04 100644 --- a/src/config.cc +++ b/src/config.cc @@ -1,3 +1,5 @@ +#include +#include #include #include #include @@ -7,24 +9,70 @@ #include "config.h" #include "utils.h" -vec cfg_path({"lsvd.conf", "/usr/local/etc/lsvd.conf"}); +lsvd_config lsvd_config::get_default() { return lsvd_config(); } -opt lsvd_config::from_file(str path) +opt lsvd_config::from_user_cfg(str cfg) { - // write just a simple braindead parser - // syntax: lines of `key = value` + auto c = get_default(); + if (cfg.empty()) + return c; + + try { + c.parse_file("/usr/local/etc/lsvd.json", true); + c.parse_file("./lsvd.json", true); - UNIMPLEMENTED(); + if (cfg[0] == '{') { + c.parse_json(cfg); + } else { + c.parse_file(cfg, false); + } + return c; + } catch (const std::exception &e) { + log_error("Failed to parse config '{}': {}", cfg, e.what()); + return std::nullopt; + } } -lsvd_config lsvd_config::get_default() { return lsvd_config(); } +// https://stackoverflow.com/questions/116038/how-do-i-read-an-entire-file-into-a-stdstring-in-c +auto read_file(std::string_view path) -> std::string +{ + constexpr auto read_size = std::size_t(4096); + auto stream = std::ifstream(path.data()); + stream.exceptions(std::ios_base::badbit); + + if (not stream) + throw std::ios_base::failure("file does not exist"); + + auto out = std::string(); + auto buf = std::string(read_size, '\0'); + while (stream.read(&buf[0], read_size)) + out.append(buf, 0, stream.gcount()); + out.append(buf, 0, stream.gcount()); + return out; +} + +void lsvd_config::parse_file(str path, bool can_be_missing) +{ + assert(!path.empty()); + if (access(path.c_str(), F_OK) == -1) { + if (can_be_missing) { + log_info("Optional config file missing: {}, continuing...", path); + return; + } + log_error("Config file not found: {}", path); + return; + } + + auto cfg = read_file(path); + parse_json(cfg); +} #define JSON_GET_BOOL(key) \ do { \ if (js.contains(#key)) { \ auto v = js[#key]; \ if (v.is_boolean()) { \ - cfg.key = js[#key]; \ + this->key = js[#key]; \ } else { \ log_error("Invalid value for key (must be bool): {}", #key); \ } \ @@ -36,7 +84,7 @@ lsvd_config lsvd_config::get_default() { return lsvd_config(); } if (js.contains(#key)) { \ auto v = js[#key]; \ if (v.is_number_unsigned()) { \ - cfg.key = js[#key]; \ + this->key = js[#key]; \ } else { \ log_error("Invalid value for key (must be uint): {}", #key); \ } \ @@ -48,25 +96,17 @@ lsvd_config lsvd_config::get_default() { return lsvd_config(); } if (js.contains(#key)) { \ auto v = js[#key]; \ if (v.is_string()) { \ - cfg.key = js[#key]; \ + this->key = js[#key]; \ } else { \ log_error("Invalid value for key (must be str): {}", #key); \ } \ } \ } while (0) -opt lsvd_config::from_json(str json) +void lsvd_config::parse_json(str json) { - nlohmann::json js; + auto js = nlohmann::json::parse(json); - try { - js = nlohmann::json::parse(json); - } catch (nlohmann::json::parse_error &e) { - log_error("Failed to parse JSON: {}", e.what()); - return std::nullopt; - } - - lsvd_config cfg; JSON_GET_STR(rcache_dir); JSON_GET_UINT(rcache_bytes); JSON_GET_UINT(rcache_fetch_window); @@ -86,6 +126,4 @@ opt lsvd_config::from_json(str json) JSON_GET_UINT(gc_threshold_pc); JSON_GET_UINT(gc_write_window); JSON_GET_BOOL(no_gc); - - return cfg; } diff --git a/src/config.h b/src/config.h index e7c602cf..03b5efb3 100644 --- a/src/config.h +++ b/src/config.h @@ -40,10 +40,17 @@ class lsvd_config return fspath(wlog_dir) / filename; } - static opt from_json(str json); - static opt from_file(str path); + /** + * Read LSVD configuration from user-supplied string. The string can be + * either a json string containing the configuration, the path to a file + * containing the same, or empty, in which case it will be ignored. + */ + static opt from_user_cfg(str cfg); static lsvd_config get_default(); private: lsvd_config() {} + + void parse_json(str js); + void parse_file(str path, bool can_be_missing = true); }; diff --git a/src/image.cc b/src/image.cc index 9eab7bea..b92097ec 100644 --- a/src/image.cc +++ b/src/image.cc @@ -19,7 +19,8 @@ lsvd_image::lsvd_image(std::string name, rados_ioctx_t io, lsvd_config cfg_) : imgname(name), cfg(cfg_), io(io) { objstore = make_rados_backend(io); - rcache = get_read_cache_instance(cfg.rcache_dir, cfg.rcache_size, objstore); + rcache = + get_read_cache_instance(cfg.rcache_dir, cfg.rcache_bytes, objstore); read_superblock(); debug("Found checkpoints: {}", checkpoints); diff --git a/src/spdk_frontend.cc b/src/spdk_frontend.cc index 75b9cd08..5c07e8c1 100644 --- a/src/spdk_frontend.cc +++ b/src/spdk_frontend.cc @@ -181,7 +181,8 @@ static void start_lsvd(void *arg) auto trid = get_trid(HOSTNAME, PORT); // Add lsvd bdev - lsvd_config cfg; // TODO read this in from a config file + auto cfg = + lsvd_config::get_default(); // TODO read this in from a config file cfg.rcache_bytes = 160 * 1024 * 1024; // small 160mb cache for testing auto err = bdev_lsvd_create(args->image_name, io_ctx, cfg); assert(err == 0); @@ -226,7 +227,6 @@ int main(int argc, const char **argv) return 1; } - auto args = (start_lsvd_args){ .pool_name = argv[1], .image_name = argv[2], @@ -238,7 +238,6 @@ int main(int argc, const char **argv) debug("Args: pool={}, image={}", args.pool_name, args.image_name); - spdk_app_opts opts = {.shutdown_cb = []() { log_info("Shutting down LSVD SPDK program ..."); spdk_app_stop(0); diff --git a/src/translate.cc b/src/translate.cc index 76a3fd90..00b83281 100644 --- a/src/translate.cc +++ b/src/translate.cc @@ -1111,7 +1111,7 @@ void translate_impl::do_gc(std::stop_token &st) workers->put_locked(req); lk.unlock(); - while ((int)requests.size() > cfg.gc_write_window && + while (requests.size() > cfg.gc_write_window && !st.stop_requested()) { auto t = requests.front(); t->wait(); From ca3491ac56137068af7df02d96d00006bd5f2841 Mon Sep 17 00:00:00 2001 From: Isaac Khor Date: Thu, 8 Aug 2024 02:53:27 +0000 Subject: [PATCH 06/12] Add json dependency wrap --- .gitignore | 2 +- subprojects/nlohmann_json.wrap | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 subprojects/nlohmann_json.wrap diff --git a/.gitignore b/.gitignore index 8be2b408..1a1e4d55 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,4 @@ qemu/bzImage subprojects/liburing-* subprojects/packagecache -subprojects/nlohmann_json* +subprojects/nlohmann_json-* diff --git a/subprojects/nlohmann_json.wrap b/subprojects/nlohmann_json.wrap new file mode 100644 index 00000000..bf5d7000 --- /dev/null +++ b/subprojects/nlohmann_json.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = nlohmann_json-3.11.2 +lead_directory_missing = true +source_url = https://github.com/nlohmann/json/releases/download/v3.11.2/include.zip +source_filename = nlohmann_json-3.11.2.zip +source_hash = e5c7a9f49a16814be27e4ed0ee900ecd0092bfb7dbfca65b5a421b774dccaaed +wrapdb_version = 3.11.2-1 + +[provide] +nlohmann_json = nlohmann_json_dep From 360252f93f0cff3e7cbcf9d02b77ba7867cf398f Mon Sep 17 00:00:00 2001 From: Isaac Khor Date: Thu, 8 Aug 2024 03:03:47 +0000 Subject: [PATCH 07/12] Remove extraneous newlines --- src/bdev_lsvd_rpc.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bdev_lsvd_rpc.cc b/src/bdev_lsvd_rpc.cc index 067ed163..bba64d5b 100644 --- a/src/bdev_lsvd_rpc.cc +++ b/src/bdev_lsvd_rpc.cc @@ -44,13 +44,13 @@ static void rpc_bdev_lsvd_create(spdk_jsonrpc_request *req_json, auto rc = spdk_json_decode_object(params, rpc_create_lsvd_decoders, SPDK_COUNTOF(rpc_create_lsvd_decoders), req.get()); - PR_GOTO_IF(rc != 0, fail, "spdk_json_decode_object failed\n"); + PR_GOTO_IF(rc != 0, fail, "spdk_json_decode_object failed"); rc = bdev_lsvd_create(req->pool_name, req->image_name, req->config); - PR_GOTO_IF(rc != 0, fail, "failed to create lsvd bdev\n"); + PR_GOTO_IF(rc != 0, fail, "failed to create lsvd bdev"); w = spdk_jsonrpc_begin_result(req_json); - PR_GOTO_IF(w == nullptr, fail, "failed to create json result\n"); + PR_GOTO_IF(w == nullptr, fail, "failed to create json result"); spdk_json_write_bool(w, true); spdk_jsonrpc_end_result(req_json, w); @@ -81,13 +81,13 @@ static void rpc_bdev_lsvd_delete(struct spdk_jsonrpc_request *req_json, int rc = spdk_json_decode_object(params, rpc_delete_lsvd_decoders, SPDK_COUNTOF(rpc_delete_lsvd_decoders), req.get()); - PR_GOTO_IF(rc != 0, fail, "failed to decode json object\n"); + PR_GOTO_IF(rc != 0, fail, "failed to decode json object"); rc = bdev_lsvd_delete(req->image_name); PR_GOTO_IF(rc != 0, fail, "failed to destroy lsvd bdev"); w = spdk_jsonrpc_begin_result(req_json); - PR_GOTO_IF(w == nullptr, fail, "failed to create json result\n"); + PR_GOTO_IF(w == nullptr, fail, "failed to create json result"); spdk_json_write_bool(w, true); spdk_jsonrpc_end_result(req_json, w); From 37f9b72b13109cdd779479f1879aeac11687e765 Mon Sep 17 00:00:00 2001 From: Isaac Khor Date: Fri, 9 Aug 2024 00:54:45 +0000 Subject: [PATCH 08/12] Fix json parsing errors --- src/bdev_lsvd.cc | 4 + src/bdev_lsvd_rpc.cc | 13 +- src/expected.hpp | 2441 ++++++++++++++++++++++++++++++++++++++++++ src/rpc_plugin.py | 10 +- 4 files changed, 2455 insertions(+), 13 deletions(-) create mode 100644 src/expected.hpp diff --git a/src/bdev_lsvd.cc b/src/bdev_lsvd.cc index 734ff5b8..0fdc0a1b 100644 --- a/src/bdev_lsvd.cc +++ b/src/bdev_lsvd.cc @@ -230,10 +230,14 @@ int bdev_lsvd_create(str pool_name, str image_name, str user_cfg) int bdev_lsvd_delete(str img_name) { + // TODO this currently deadlocks because of spdk's threading model, need to + // make it async + log_info("Deleting image '{}'", img_name); auto p = std::promise(); spdk_bdev_unregister_by_name( img_name.c_str(), &lsvd_if, [](void *arg, int rc) { + log_info("Image deletion complete, rc = {}", rc); auto p = (std::promise *)arg; p->set_value(rc); }, diff --git a/src/bdev_lsvd_rpc.cc b/src/bdev_lsvd_rpc.cc index bba64d5b..7689b146 100644 --- a/src/bdev_lsvd_rpc.cc +++ b/src/bdev_lsvd_rpc.cc @@ -22,12 +22,9 @@ struct rpc_create_lsvd { }; static const struct spdk_json_object_decoder rpc_create_lsvd_decoders[] = { - {"image_name", offsetof(rpc_create_lsvd, image_name), - spdk_json_decode_string, false}, - {"pool_name", offsetof(rpc_create_lsvd, pool_name), spdk_json_decode_string, - false}, - {"config", offsetof(rpc_create_lsvd, config), spdk_json_decode_string, - true}, + {"image_name", offsetof(rpc_create_lsvd, image_name), spdk_json_decode_string, false}, + {"pool_name", offsetof(rpc_create_lsvd, pool_name), spdk_json_decode_string, false}, + {"config", offsetof(rpc_create_lsvd, config), spdk_json_decode_string, true}, }; static void rpc_bdev_lsvd_create(spdk_jsonrpc_request *req_json, @@ -53,6 +50,7 @@ static void rpc_bdev_lsvd_create(spdk_jsonrpc_request *req_json, PR_GOTO_IF(w == nullptr, fail, "failed to create json result"); spdk_json_write_bool(w, true); spdk_jsonrpc_end_result(req_json, w); + return; fail: spdk_jsonrpc_send_error_response(req_json, rc, @@ -66,8 +64,7 @@ struct rpc_delete_lsvd { }; static const struct spdk_json_object_decoder rpc_delete_lsvd_decoders[] = { - {"image_name", offsetof(struct rpc_delete_lsvd, image_name), - spdk_json_decode_string, false}, + {"image_name", offsetof(struct rpc_delete_lsvd, image_name), spdk_json_decode_string, false}, }; static void rpc_bdev_lsvd_delete(struct spdk_jsonrpc_request *req_json, diff --git a/src/expected.hpp b/src/expected.hpp new file mode 100644 index 00000000..6d674daf --- /dev/null +++ b/src/expected.hpp @@ -0,0 +1,2441 @@ +/// +// expected - An implementation of std::expected with extensions +// Written in 2017 by Sy Brand (tartanllama@gmail.com, @TartanLlama) +// +// Documentation available at http://tl.tartanllama.xyz/ +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +/// + +#pragma once + +#define TL_EXPECTED_VERSION_MAJOR 1 +#define TL_EXPECTED_VERSION_MINOR 1 +#define TL_EXPECTED_VERSION_PATCH 0 + +#include +#include +#include +#include + +#if defined(__EXCEPTIONS) || defined(_CPPUNWIND) +#define TL_EXPECTED_EXCEPTIONS_ENABLED +#endif + +#if (defined(_MSC_VER) && _MSC_VER == 1900) +#define TL_EXPECTED_MSVC2015 +#define TL_EXPECTED_MSVC2015_CONSTEXPR +#else +#define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +#define TL_EXPECTED_GCC49 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ + !defined(__clang__)) +#define TL_EXPECTED_GCC54 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ + !defined(__clang__)) +#define TL_EXPECTED_GCC55 +#endif + +#if !defined(TL_ASSERT) +//can't have assert in constexpr in C++11 and GCC 4.9 has a compiler bug +#if (__cplusplus > 201103L) && !defined(TL_EXPECTED_GCC49) +#include +#define TL_ASSERT(x) assert(x) +#else +#define TL_ASSERT(x) +#endif +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +// GCC < 5 doesn't support overloading on const&& for member functions + +#define TL_EXPECTED_NO_CONSTRR +// GCC < 5 doesn't support some standard C++11 type traits +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::has_trivial_copy_constructor +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::has_trivial_copy_assign + +// This one will be different for GCC 5.7 if it's ever supported +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible + +// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks +// std::vector for non-copyable types +#elif (defined(__GNUC__) && __GNUC__ < 8 && !defined(__clang__)) +#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +namespace tl { +namespace detail { +template +struct is_trivially_copy_constructible + : std::is_trivially_copy_constructible {}; +#ifdef _GLIBCXX_VECTOR +template +struct is_trivially_copy_constructible> : std::false_type {}; +#endif +} // namespace detail +} // namespace tl +#endif + +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + tl::detail::is_trivially_copy_constructible +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible +#else +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::is_trivially_copy_constructible +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible +#endif + +#if __cplusplus > 201103L +#define TL_EXPECTED_CXX14 +#endif + +#ifdef TL_EXPECTED_GCC49 +#define TL_EXPECTED_GCC49_CONSTEXPR +#else +#define TL_EXPECTED_GCC49_CONSTEXPR constexpr +#endif + +#if (__cplusplus == 201103L || defined(TL_EXPECTED_MSVC2015) || \ + defined(TL_EXPECTED_GCC49)) +#define TL_EXPECTED_11_CONSTEXPR +#else +#define TL_EXPECTED_11_CONSTEXPR constexpr +#endif + +namespace tl { +template class expected; + +#ifndef TL_MONOSTATE_INPLACE_MUTEX +#define TL_MONOSTATE_INPLACE_MUTEX +class monostate {}; + +struct in_place_t { + explicit in_place_t() = default; +}; +static constexpr in_place_t in_place{}; +#endif + +template class unexpected { +public: + static_assert(!std::is_same::value, "E must not be void"); + + unexpected() = delete; + constexpr explicit unexpected(const E &e) : m_val(e) {} + + constexpr explicit unexpected(E &&e) : m_val(std::move(e)) {} + + template ::value>::type * = nullptr> + constexpr explicit unexpected(Args &&...args) + : m_val(std::forward(args)...) {} + template < + class U, class... Args, + typename std::enable_if &, Args &&...>::value>::type * = nullptr> + constexpr explicit unexpected(std::initializer_list l, Args &&...args) + : m_val(l, std::forward(args)...) {} + + constexpr const E &value() const & { return m_val; } + TL_EXPECTED_11_CONSTEXPR E &value() & { return m_val; } + TL_EXPECTED_11_CONSTEXPR E &&value() && { return std::move(m_val); } + constexpr const E &&value() const && { return std::move(m_val); } + +private: + E m_val; +}; + +#ifdef __cpp_deduction_guides +template unexpected(E) -> unexpected; +#endif + +template +constexpr bool operator==(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() == rhs.value(); +} +template +constexpr bool operator!=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() != rhs.value(); +} +template +constexpr bool operator<(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() < rhs.value(); +} +template +constexpr bool operator<=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() <= rhs.value(); +} +template +constexpr bool operator>(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() > rhs.value(); +} +template +constexpr bool operator>=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() >= rhs.value(); +} + +template +unexpected::type> make_unexpected(E &&e) { + return unexpected::type>(std::forward(e)); +} + +struct unexpect_t { + unexpect_t() = default; +}; +static constexpr unexpect_t unexpect{}; + +namespace detail { +template +[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) { +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + throw std::forward(e); +#else + (void)e; +#ifdef _MSC_VER + __assume(0); +#else + __builtin_unreachable(); +#endif +#endif +} + +#ifndef TL_TRAITS_MUTEX +#define TL_TRAITS_MUTEX +// C++14-style aliases for brevity +template using remove_const_t = typename std::remove_const::type; +template +using remove_reference_t = typename std::remove_reference::type; +template using decay_t = typename std::decay::type; +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; + +// std::conjunction from C++17 +template struct conjunction : std::true_type {}; +template struct conjunction : B {}; +template +struct conjunction + : std::conditional, B>::type {}; + +#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L +#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +#endif + +// In C++11 mode, there's an issue in libc++'s std::mem_fn +// which results in a hard-error when using it in a noexcept expression +// in some cases. This is a check to workaround the common failing case. +#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +template +struct is_pointer_to_non_const_member_func : std::false_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; + +template struct is_const_or_const_ref : std::false_type {}; +template struct is_const_or_const_ref : std::true_type {}; +template struct is_const_or_const_ref : std::true_type {}; +#endif + +// std::invoke from C++17 +// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround +template < + typename Fn, typename... Args, +#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND + typename = enable_if_t::value && + is_const_or_const_ref::value)>, +#endif + typename = enable_if_t>::value>, int = 0> +constexpr auto invoke(Fn &&f, Args &&...args) noexcept( + noexcept(std::mem_fn(f)(std::forward(args)...))) + -> decltype(std::mem_fn(f)(std::forward(args)...)) { + return std::mem_fn(f)(std::forward(args)...); +} + +template >::value>> +constexpr auto invoke(Fn &&f, Args &&...args) noexcept( + noexcept(std::forward(f)(std::forward(args)...))) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); +} + +// std::invoke_result from C++17 +template struct invoke_result_impl; + +template +struct invoke_result_impl< + F, + decltype(detail::invoke(std::declval(), std::declval()...), void()), + Us...> { + using type = + decltype(detail::invoke(std::declval(), std::declval()...)); +}; + +template +using invoke_result = invoke_result_impl; + +template +using invoke_result_t = typename invoke_result::type; + +#if defined(_MSC_VER) && _MSC_VER <= 1900 +// TODO make a version which works with MSVC 2015 +template struct is_swappable : std::true_type {}; + +template struct is_nothrow_swappable : std::true_type {}; +#else +// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept +namespace swap_adl_tests { +// if swap ADL finds this then it would call std::swap otherwise (same +// signature) +struct tag {}; + +template tag swap(T &, T &); +template tag swap(T (&a)[N], T (&b)[N]); + +// helper functions to test if an unqualified swap is possible, and if it +// becomes std::swap +template std::false_type can_swap(...) noexcept(false); +template (), std::declval()))> +std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), + std::declval()))); + +template std::false_type uses_std(...); +template +std::is_same(), std::declval())), tag> +uses_std(int); + +template +struct is_std_swap_noexcept + : std::integral_constant::value && + std::is_nothrow_move_assignable::value> {}; + +template +struct is_std_swap_noexcept : is_std_swap_noexcept {}; + +template +struct is_adl_swap_noexcept + : std::integral_constant(0))> {}; +} // namespace swap_adl_tests + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std(0))::value || + (std::is_move_assignable::value && + std::is_move_constructible::value))> {}; + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std( + 0))::value || + is_swappable::value)> {}; + +template +struct is_nothrow_swappable + : std::integral_constant< + bool, + is_swappable::value && + ((decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_std_swap_noexcept::value) || + (!decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_adl_swap_noexcept::value))> {}; +#endif +#endif + +// Trait for checking if a type is a tl::expected +template struct is_expected_impl : std::false_type {}; +template +struct is_expected_impl> : std::true_type {}; +template using is_expected = is_expected_impl>; + +template +using expected_enable_forward_value = detail::enable_if_t< + std::is_constructible::value && + !std::is_same, in_place_t>::value && + !std::is_same, detail::decay_t>::value && + !std::is_same, detail::decay_t>::value>; + +template +using expected_enable_from_other = detail::enable_if_t< + std::is_constructible::value && + std::is_constructible::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value>; + +template +using is_void_or = conditional_t::value, std::true_type, U>; + +template +using is_copy_constructible_or_void = + is_void_or>; + +template +using is_move_constructible_or_void = + is_void_or>; + +template +using is_copy_assignable_or_void = is_void_or>; + +template +using is_move_assignable_or_void = is_void_or>; + +} // namespace detail + +namespace detail { +struct no_init_t {}; +static constexpr no_init_t no_init{}; + +// Implements the storage of the values, and ensures that the destructor is +// trivial if it can be. +// +// This specialization is for where neither `T` or `E` is trivially +// destructible, so the destructors must be called on destruction of the +// `expected` +template ::value, + bool = std::is_trivially_destructible::value> +struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&...args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&...args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (m_has_val) { + m_val.~T(); + } else { + m_unexpect.~unexpected(); + } + } + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// This specialization is for when both `T` and `E` are trivially-destructible, +// so the destructor of the `expected` can be trivial. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&...args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&...args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() = default; + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// T is trivial, E is not. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t) + : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&...args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&...args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (!m_has_val) { + m_unexpect.~unexpected(); + } + } + + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// E is trivial, T is not. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&...args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&...args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (m_has_val) { + m_val.~T(); + } + } + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// `T` is `void`, `E` is trivially-destructible +template struct expected_storage_base { + #if __GNUC__ <= 5 + //no constexpr for GCC 4/5 bug + #else + TL_EXPECTED_MSVC2015_CONSTEXPR + #endif + expected_storage_base() : m_has_val(true) {} + + constexpr expected_storage_base(no_init_t) : m_val(), m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : m_has_val(true) {} + + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() = default; + struct dummy {}; + union { + unexpected m_unexpect; + dummy m_val; + }; + bool m_has_val; +}; + +// `T` is `void`, `E` is not trivially-destructible +template struct expected_storage_base { + constexpr expected_storage_base() : m_dummy(), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_dummy(), m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : m_dummy(), m_has_val(true) {} + + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (!m_has_val) { + m_unexpect.~unexpected(); + } + } + + union { + unexpected m_unexpect; + char m_dummy; + }; + bool m_has_val; +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template +struct expected_operations_base : expected_storage_base { + using expected_storage_base::expected_storage_base; + + template void construct(Args &&...args) noexcept { + new (std::addressof(this->m_val)) T(std::forward(args)...); + this->m_has_val = true; + } + + template void construct_with(Rhs &&rhs) noexcept { + new (std::addressof(this->m_val)) T(std::forward(rhs).get()); + this->m_has_val = true; + } + + template void construct_error(Args &&...args) noexcept { + new (std::addressof(this->m_unexpect)) + unexpected(std::forward(args)...); + this->m_has_val = false; + } + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + + // These assign overloads ensure that the most efficient assignment + // implementation is used while maintaining the strong exception guarantee. + // The problematic case is where rhs has a value, but *this does not. + // + // This overload handles the case where we can just copy-construct `T` + // directly into place without throwing. + template ::value> + * = nullptr> + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(rhs.get()); + } else { + assign_common(rhs); + } + } + + // This overload handles the case where we can attempt to create a copy of + // `T`, then no-throw move it into place if the copy was successful. + template ::value && + std::is_nothrow_move_constructible::value> + * = nullptr> + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + T tmp = rhs.get(); + geterr().~unexpected(); + construct(std::move(tmp)); + } else { + assign_common(rhs); + } + } + + // This overload is the worst-case, where we have to move-construct the + // unexpected value into temporary storage, then try to copy the T into place. + // If the construction succeeds, then everything is fine, but if it throws, + // then we move the old unexpected value back into place before rethrowing the + // exception. + template ::value && + !std::is_nothrow_move_constructible::value> + * = nullptr> + void assign(const expected_operations_base &rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(geterr()); + geterr().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + construct(rhs.get()); + } catch (...) { + geterr() = std::move(tmp); + throw; + } +#else + construct(rhs.get()); +#endif + } else { + assign_common(rhs); + } + } + + // These overloads do the same as above, but for rvalues + template ::value> + * = nullptr> + void assign(expected_operations_base &&rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(std::move(rhs).get()); + } else { + assign_common(std::move(rhs)); + } + } + + template ::value> + * = nullptr> + void assign(expected_operations_base &&rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(geterr()); + geterr().~unexpected(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + construct(std::move(rhs).get()); + } catch (...) { + geterr() = std::move(tmp); + throw; + } +#else + construct(std::move(rhs).get()); +#endif + } else { + assign_common(std::move(rhs)); + } + } + +#else + + // If exceptions are disabled then we can just copy-construct + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(rhs.get()); + } else { + assign_common(rhs); + } + } + + void assign(expected_operations_base &&rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(std::move(rhs).get()); + } else { + assign_common(std::move(rhs)); + } + } + +#endif + + // The common part of move/copy assigning + template void assign_common(Rhs &&rhs) { + if (this->m_has_val) { + if (rhs.m_has_val) { + get() = std::forward(rhs).get(); + } else { + destroy_val(); + construct_error(std::forward(rhs).geterr()); + } + } else { + if (!rhs.m_has_val) { + geterr() = std::forward(rhs).geterr(); + } + } + } + + bool has_value() const { return this->m_has_val; } + + TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; } + constexpr const T &get() const & { return this->m_val; } + TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const T &&get() const && { return std::move(this->m_val); } +#endif + + TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { + return this->m_unexpect; + } + constexpr const unexpected &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { + return std::move(this->m_unexpect); + } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const unexpected &&geterr() const && { + return std::move(this->m_unexpect); + } +#endif + + TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); } +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template +struct expected_operations_base : expected_storage_base { + using expected_storage_base::expected_storage_base; + + template void construct() noexcept { this->m_has_val = true; } + + // This function doesn't use its argument, but needs it so that code in + // levels above this can work independently of whether T is void + template void construct_with(Rhs &&) noexcept { + this->m_has_val = true; + } + + template void construct_error(Args &&...args) noexcept { + new (std::addressof(this->m_unexpect)) + unexpected(std::forward(args)...); + this->m_has_val = false; + } + + template void assign(Rhs &&rhs) noexcept { + if (!this->m_has_val) { + if (rhs.m_has_val) { + geterr().~unexpected(); + construct(); + } else { + geterr() = std::forward(rhs).geterr(); + } + } else { + if (!rhs.m_has_val) { + construct_error(std::forward(rhs).geterr()); + } + } + } + + bool has_value() const { return this->m_has_val; } + + TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { + return this->m_unexpect; + } + constexpr const unexpected &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { + return std::move(this->m_unexpect); + } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const unexpected &&geterr() const && { + return std::move(this->m_unexpect); + } +#endif + + TL_EXPECTED_11_CONSTEXPR void destroy_val() { + // no-op + } +}; + +// This class manages conditionally having a trivial copy constructor +// This specialization is for when T and E are trivially copy constructible +template :: + value &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value> +struct expected_copy_base : expected_operations_base { + using expected_operations_base::expected_operations_base; +}; + +// This specialization is for when T or E are not trivially copy constructible +template +struct expected_copy_base : expected_operations_base { + using expected_operations_base::expected_operations_base; + + expected_copy_base() = default; + expected_copy_base(const expected_copy_base &rhs) + : expected_operations_base(no_init) { + if (rhs.has_value()) { + this->construct_with(rhs); + } else { + this->construct_error(rhs.geterr()); + } + } + + expected_copy_base(expected_copy_base &&rhs) = default; + expected_copy_base &operator=(const expected_copy_base &rhs) = default; + expected_copy_base &operator=(expected_copy_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move constructor +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_constructible. We +// have to make do with a non-trivial move constructor even if T is trivially +// move constructible +#ifndef TL_EXPECTED_GCC49 +template >::value + &&std::is_trivially_move_constructible::value> +struct expected_move_base : expected_copy_base { + using expected_copy_base::expected_copy_base; +}; +#else +template struct expected_move_base; +#endif +template +struct expected_move_base : expected_copy_base { + using expected_copy_base::expected_copy_base; + + expected_move_base() = default; + expected_move_base(const expected_move_base &rhs) = default; + + expected_move_base(expected_move_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value) + : expected_copy_base(no_init) { + if (rhs.has_value()) { + this->construct_with(std::move(rhs)); + } else { + this->construct_error(std::move(rhs.geterr())); + } + } + expected_move_base &operator=(const expected_move_base &rhs) = default; + expected_move_base &operator=(expected_move_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial copy assignment operator +template >::value + &&TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value + &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value + &&TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value> +struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; +}; + +template +struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; + + expected_copy_assign_base() = default; + expected_copy_assign_base(const expected_copy_assign_base &rhs) = default; + + expected_copy_assign_base(expected_copy_assign_base &&rhs) = default; + expected_copy_assign_base &operator=(const expected_copy_assign_base &rhs) { + this->assign(rhs); + return *this; + } + expected_copy_assign_base & + operator=(expected_copy_assign_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move assignment operator +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_assignable. We have +// to make do with a non-trivial move assignment operator even if T is trivially +// move assignable +#ifndef TL_EXPECTED_GCC49 +template , + std::is_trivially_move_constructible, + std::is_trivially_move_assignable>>:: + value &&std::is_trivially_destructible::value + &&std::is_trivially_move_constructible::value + &&std::is_trivially_move_assignable::value> +struct expected_move_assign_base : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; +}; +#else +template struct expected_move_assign_base; +#endif + +template +struct expected_move_assign_base + : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; + + expected_move_assign_base() = default; + expected_move_assign_base(const expected_move_assign_base &rhs) = default; + + expected_move_assign_base(expected_move_assign_base &&rhs) = default; + + expected_move_assign_base & + operator=(const expected_move_assign_base &rhs) = default; + + expected_move_assign_base & + operator=(expected_move_assign_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value + &&std::is_nothrow_move_assignable::value) { + this->assign(std::move(rhs)); + return *this; + } +}; + +// expected_delete_ctor_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible +template ::value && + std::is_copy_constructible::value), + bool EnableMove = (is_move_constructible_or_void::value && + std::is_move_constructible::value)> +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +// expected_delete_assign_base will conditionally delete copy and move +// constructors depending on whether T and E are copy/move constructible + +// assignable +template ::value && + std::is_copy_constructible::value && + is_copy_assignable_or_void::value && + std::is_copy_assignable::value), + bool EnableMove = (is_move_constructible_or_void::value && + std::is_move_constructible::value && + is_move_assignable_or_void::value && + std::is_move_assignable::value)> +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = default; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = delete; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = default; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = delete; +}; + +// This is needed to be able to construct the expected_default_ctor_base which +// follows, while still conditionally deleting the default constructor. +struct default_constructor_tag { + explicit constexpr default_constructor_tag() = default; +}; + +// expected_default_ctor_base will ensure that expected has a deleted default +// consturctor if T is not default constructible. +// This specialization is for when T is default constructible +template ::value || std::is_void::value> +struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = default; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; + +// This specialization is for when T is not default constructible +template struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = delete; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; +} // namespace detail + +template class bad_expected_access : public std::exception { +public: + explicit bad_expected_access(E e) : m_val(std::move(e)) {} + + virtual const char *what() const noexcept override { + return "Bad expected access"; + } + + const E &error() const & { return m_val; } + E &error() & { return m_val; } + const E &&error() const && { return std::move(m_val); } + E &&error() && { return std::move(m_val); } + +private: + E m_val; +}; + +/// An `expected` object is an object that contains the storage for +/// another object and manages the lifetime of this contained object `T`. +/// Alternatively it could contain the storage for another unexpected object +/// `E`. The contained object may not be initialized after the expected object +/// has been initialized, and may not be destroyed before the expected object +/// has been destroyed. The initialization state of the contained object is +/// tracked by the expected object. +template +class expected : private detail::expected_move_assign_base, + private detail::expected_delete_ctor_base, + private detail::expected_delete_assign_base, + private detail::expected_default_ctor_base { + static_assert(!std::is_reference::value, "T must not be a reference"); + static_assert(!std::is_same::type>::value, + "T must not be in_place_t"); + static_assert(!std::is_same::type>::value, + "T must not be unexpect_t"); + static_assert( + !std::is_same>::type>::value, + "T must not be unexpected"); + static_assert(!std::is_reference::value, "E must not be a reference"); + + T *valptr() { return std::addressof(this->m_val); } + const T *valptr() const { return std::addressof(this->m_val); } + unexpected *errptr() { return std::addressof(this->m_unexpect); } + const unexpected *errptr() const { + return std::addressof(this->m_unexpect); + } + + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &val() { + return this->m_val; + } + TL_EXPECTED_11_CONSTEXPR unexpected &err() { return this->m_unexpect; } + + template ::value> * = nullptr> + constexpr const U &val() const { + return this->m_val; + } + constexpr const unexpected &err() const { return this->m_unexpect; } + + using impl_base = detail::expected_move_assign_base; + using ctor_base = detail::expected_default_ctor_base; + +public: + typedef T value_type; + typedef E error_type; + typedef unexpected unexpected_type; + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { + return and_then_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { + return and_then_impl(std::move(*this), std::forward(f)); + } + template constexpr auto and_then(F &&f) const & { + return and_then_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template constexpr auto and_then(F &&f) const && { + return and_then_impl(std::move(*this), std::forward(f)); + } +#endif + +#else + template + TL_EXPECTED_11_CONSTEXPR auto + and_then(F &&f) & -> decltype(and_then_impl(std::declval(), + std::forward(f))) { + return and_then_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR auto + and_then(F &&f) && -> decltype(and_then_impl(std::declval(), + std::forward(f))) { + return and_then_impl(std::move(*this), std::forward(f)); + } + template + constexpr auto and_then(F &&f) const & -> decltype(and_then_impl( + std::declval(), std::forward(f))) { + return and_then_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr auto and_then(F &&f) const && -> decltype(and_then_impl( + std::declval(), std::forward(f))) { + return and_then_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template constexpr auto map(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + template constexpr auto map(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( + std::declval(), std::declval())) + map(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template constexpr auto transform(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + template constexpr auto transform(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( + std::declval(), std::declval())) + transform(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), + std::declval())) + transform(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + transform(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + transform(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template constexpr auto map_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + template constexpr auto map_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#endif +#endif +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template constexpr auto transform_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + template constexpr auto transform_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & { + return or_else_impl(*this, std::forward(f)); + } + + template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && { + return or_else_impl(std::move(*this), std::forward(f)); + } + + template expected constexpr or_else(F &&f) const & { + return or_else_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template expected constexpr or_else(F &&f) const && { + return or_else_impl(std::move(*this), std::forward(f)); + } +#endif + constexpr expected() = default; + constexpr expected(const expected &rhs) = default; + constexpr expected(expected &&rhs) = default; + expected &operator=(const expected &rhs) = default; + expected &operator=(expected &&rhs) = default; + + template ::value> * = + nullptr> + constexpr expected(in_place_t, Args &&...args) + : impl_base(in_place, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected(in_place_t, std::initializer_list il, Args &&...args) + : impl_base(in_place, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value> * = + nullptr, + detail::enable_if_t::value> * = + nullptr> + explicit constexpr expected(const unexpected &e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} + + template < + class G = E, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected const &e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} + + template < + class G = E, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + explicit constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} + + template < + class G = E, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value> * = + nullptr> + constexpr explicit expected(unexpect_t, Args &&...args) + : impl_base(unexpect, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected(unexpect_t, std::initializer_list il, + Args &&...args) + : impl_base(unexpect, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value && + std::is_convertible::value)> * = + nullptr, + detail::expected_enable_from_other + * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); + } else { + this->construct_error(rhs.error()); + } + } + + template ::value && + std::is_convertible::value)> * = + nullptr, + detail::expected_enable_from_other + * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); + } else { + this->construct_error(rhs.error()); + } + } + + template < + class U, class G, + detail::enable_if_t::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } else { + this->construct_error(std::move(rhs.error())); + } + } + + template < + class U, class G, + detail::enable_if_t<(std::is_convertible::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } else { + this->construct_error(std::move(rhs.error())); + } + } + + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward(v)) {} + + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward(v)) {} + + template < + class U = T, class G = T, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } else { + err().~unexpected(); + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + } + + return *this; + } + + template < + class U = T, class G = T, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } +#else + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; +#endif + } + + return *this; + } + + template ::value && + std::is_assignable::value> * = nullptr> + expected &operator=(const unexpected &rhs) { + if (!has_value()) { + err() = rhs; + } else { + this->destroy_val(); + ::new (errptr()) unexpected(rhs); + this->m_has_val = false; + } + + return *this; + } + + template ::value && + std::is_move_assignable::value> * = nullptr> + expected &operator=(unexpected &&rhs) noexcept { + if (!has_value()) { + err() = std::move(rhs); + } else { + this->destroy_val(); + ::new (errptr()) unexpected(std::move(rhs)); + this->m_has_val = false; + } + + return *this; + } + + template ::value> * = nullptr> + void emplace(Args &&...args) { + if (has_value()) { + val().~T(); + } else { + err().~unexpected(); + this->m_has_val = true; + } + ::new (valptr()) T(std::forward(args)...); + } + + template ::value> * = nullptr> + void emplace(Args &&...args) { + if (has_value()) { + val().~T(); + ::new (valptr()) T(std::forward(args)...); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } +#else + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; +#endif + } + } + + template &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&...args) { + if (has_value()) { + T t(il, std::forward(args)...); + val() = std::move(t); + } else { + err().~unexpected(); + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } + } + + template &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&...args) { + if (has_value()) { + T t(il, std::forward(args)...); + val() = std::move(t); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } +#else + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; +#endif + } + } + +private: + using t_is_void = std::true_type; + using t_is_not_void = std::false_type; + using t_is_nothrow_move_constructible = std::true_type; + using move_constructing_t_can_throw = std::false_type; + using e_is_nothrow_move_constructible = std::true_type; + using move_constructing_e_can_throw = std::false_type; + + void swap_where_both_have_value(expected & /*rhs*/, t_is_void) noexcept { + // swapping void is a no-op + } + + void swap_where_both_have_value(expected &rhs, t_is_not_void) { + using std::swap; + swap(val(), rhs.val()); + } + + void swap_where_only_one_has_value(expected &rhs, t_is_void) noexcept( + std::is_nothrow_move_constructible::value) { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + std::swap(this->m_has_val, rhs.m_has_val); + } + + void swap_where_only_one_has_value(expected &rhs, t_is_not_void) { + swap_where_only_one_has_value_and_t_is_not_void( + rhs, typename std::is_nothrow_move_constructible::type{}, + typename std::is_nothrow_move_constructible::type{}); + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, t_is_nothrow_move_constructible, + e_is_nothrow_move_constructible) noexcept { + auto temp = std::move(val()); + val().~T(); + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, t_is_nothrow_move_constructible, + move_constructing_e_can_throw) { + auto temp = std::move(val()); + val().~T(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } catch (...) { + val() = std::move(temp); + throw; + } +#else + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); +#endif + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, move_constructing_t_can_throw, + e_is_nothrow_move_constructible) { + auto temp = std::move(rhs.err()); + rhs.err().~unexpected_type(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (rhs.valptr()) T(std::move(val())); + val().~T(); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } catch (...) { + rhs.err() = std::move(temp); + throw; + } +#else + ::new (rhs.valptr()) T(std::move(val())); + val().~T(); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); +#endif + } + +public: + template + detail::enable_if_t::value && + detail::is_swappable::value && + (std::is_nothrow_move_constructible::value || + std::is_nothrow_move_constructible::value)> + swap(expected &rhs) noexcept( + std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value + &&std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value) { + if (has_value() && rhs.has_value()) { + swap_where_both_have_value(rhs, typename std::is_void::type{}); + } else if (!has_value() && rhs.has_value()) { + rhs.swap(*this); + } else if (has_value()) { + swap_where_only_one_has_value(rhs, typename std::is_void::type{}); + } else { + using std::swap; + swap(err(), rhs.err()); + } + } + + constexpr const T *operator->() const { + TL_ASSERT(has_value()); + return valptr(); + } + TL_EXPECTED_11_CONSTEXPR T *operator->() { + TL_ASSERT(has_value()); + return valptr(); + } + + template ::value> * = nullptr> + constexpr const U &operator*() const & { + TL_ASSERT(has_value()); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &operator*() & { + TL_ASSERT(has_value()); + return val(); + } + template ::value> * = nullptr> + constexpr const U &&operator*() const && { + TL_ASSERT(has_value()); + return std::move(val()); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&operator*() && { + TL_ASSERT(has_value()); + return std::move(val()); + } + + constexpr bool has_value() const noexcept { return this->m_has_val; } + constexpr explicit operator bool() const noexcept { return this->m_has_val; } + + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &value() const & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &value() & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &&value() const && { + if (!has_value()) + detail::throw_exception(bad_expected_access(std::move(err()).value())); + return std::move(val()); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&value() && { + if (!has_value()) + detail::throw_exception(bad_expected_access(std::move(err()).value())); + return std::move(val()); + } + + constexpr const E &error() const & { + TL_ASSERT(!has_value()); + return err().value(); + } + TL_EXPECTED_11_CONSTEXPR E &error() & { + TL_ASSERT(!has_value()); + return err().value(); + } + constexpr const E &&error() const && { + TL_ASSERT(!has_value()); + return std::move(err().value()); + } + TL_EXPECTED_11_CONSTEXPR E &&error() && { + TL_ASSERT(!has_value()); + return std::move(err().value()); + } + + template constexpr T value_or(U &&v) const & { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy-constructible and convertible to from U&&"); + return bool(*this) ? **this : static_cast(std::forward(v)); + } + template TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move-constructible and convertible to from U&&"); + return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); + } +}; + +namespace detail { +template using exp_t = typename detail::decay_t::value_type; +template using err_t = typename detail::decay_t::error_type; +template using ret_t = expected>; + +#ifdef TL_EXPECTED_CXX14 +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval()))> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() + ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval()))> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, std::forward(exp).error()); +} +#else +template struct TC; +template (), + *std::declval())), + detail::enable_if_t>::value> * = nullptr> +auto and_then_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() + ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, std::forward(exp).error()); +} + +template ())), + detail::enable_if_t>::value> * = nullptr> +constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, std::forward(exp).error()); +} +#endif + +#ifdef TL_EXPECTED_CXX14 +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto expected_map_impl(Exp &&exp, F &&f) { + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f), + *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +auto expected_map_impl(Exp &&exp, F &&f) { + using result = expected>; + if (exp.has_value()) { + detail::invoke(std::forward(f), *std::forward(exp)); + return result(); + } + + return result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto expected_map_impl(Exp &&exp, F &&f) { + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> +auto expected_map_impl(Exp &&exp, F &&f) { + using result = expected>; + if (exp.has_value()) { + detail::invoke(std::forward(f)); + return result(); + } + + return result(unexpect, std::forward(exp).error()); +} +#else +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto expected_map_impl(Exp &&exp, F &&f) + -> ret_t> { + using result = ret_t>; + + return exp.has_value() ? result(detail::invoke(std::forward(f), + *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto expected_map_impl(Exp &&exp, F &&f) -> expected> { + if (exp.has_value()) { + detail::invoke(std::forward(f), *std::forward(exp)); + return {}; + } + + return unexpected>(std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto expected_map_impl(Exp &&exp, F &&f) + -> ret_t> { + using result = ret_t>; + + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto expected_map_impl(Exp &&exp, F &&f) -> expected> { + if (exp.has_value()) { + detail::invoke(std::forward(f)); + return {}; + } + + return unexpected>(std::forward(exp).error()); +} +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, detail::decay_t>; + return exp.has_value() + ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, detail::decay_t>; + return exp.has_value() + ? result() + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, monostate>; + if (exp.has_value()) { + return result(); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#else +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) + -> expected, detail::decay_t> { + using result = expected, detail::decay_t>; + + return exp.has_value() + ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) + -> expected, detail::decay_t> { + using result = expected, detail::decay_t>; + + return exp.has_value() + ? result() + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { + using result = expected, monostate>; + if (exp.has_value()) { + return result(); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#endif + +#ifdef TL_EXPECTED_CXX14 +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto or_else_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + return exp.has_value() ? std::forward(exp) + : detail::invoke(std::forward(f), + std::forward(exp).error()); +} + +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +detail::decay_t or_else_impl(Exp &&exp, F &&f) { + return exp.has_value() ? std::forward(exp) + : (detail::invoke(std::forward(f), + std::forward(exp).error()), + std::forward(exp)); +} +#else +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto or_else_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + return exp.has_value() ? std::forward(exp) + : detail::invoke(std::forward(f), + std::forward(exp).error()); +} + +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +detail::decay_t or_else_impl(Exp &&exp, F &&f) { + return exp.has_value() ? std::forward(exp) + : (detail::invoke(std::forward(f), + std::forward(exp).error()), + std::forward(exp)); +} +#endif +} // namespace detail + +template +constexpr bool operator==(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? false + : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs); +} +template +constexpr bool operator!=(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? true + : (!lhs.has_value() ? lhs.error() != rhs.error() : *lhs != *rhs); +} +template +constexpr bool operator==(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? false + : (!lhs.has_value() ? lhs.error() == rhs.error() : true); +} +template +constexpr bool operator!=(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? true + : (!lhs.has_value() ? lhs.error() == rhs.error() : false); +} + +template +constexpr bool operator==(const expected &x, const U &v) { + return x.has_value() ? *x == v : false; +} +template +constexpr bool operator==(const U &v, const expected &x) { + return x.has_value() ? *x == v : false; +} +template +constexpr bool operator!=(const expected &x, const U &v) { + return x.has_value() ? *x != v : true; +} +template +constexpr bool operator!=(const U &v, const expected &x) { + return x.has_value() ? *x != v : true; +} + +template +constexpr bool operator==(const expected &x, const unexpected &e) { + return x.has_value() ? false : x.error() == e.value(); +} +template +constexpr bool operator==(const unexpected &e, const expected &x) { + return x.has_value() ? false : x.error() == e.value(); +} +template +constexpr bool operator!=(const expected &x, const unexpected &e) { + return x.has_value() ? true : x.error() != e.value(); +} +template +constexpr bool operator!=(const unexpected &e, const expected &x) { + return x.has_value() ? true : x.error() != e.value(); +} + +template ::value || + std::is_move_constructible::value) && + detail::is_swappable::value && + std::is_move_constructible::value && + detail::is_swappable::value> * = nullptr> +void swap(expected &lhs, + expected &rhs) noexcept(noexcept(lhs.swap(rhs))) { + lhs.swap(rhs); +} +} // namespace tl diff --git a/src/rpc_plugin.py b/src/rpc_plugin.py index 3a852f9d..181dd52d 100644 --- a/src/rpc_plugin.py +++ b/src/rpc_plugin.py @@ -3,24 +3,24 @@ def spdk_rpc_plugin_initialize(subparsers): def bdev_lsvd_create(args): print_json(args.client.call('bdev_lsvd_create', { - 'name': args.name, + 'image_name': args.image_name, 'pool_name': args.pool_name, - 'cfg_path': args.cfg + 'config': args.cfg or '' })) p = subparsers.add_parser('bdev_lsvd_create', help='Create a bdev with LSVD backend') p.add_argument('pool_name', help='Name of the ceph pool') - p.add_argument('name', help='Name of the lsvd disk image') + p.add_argument('image_name', help='Name of the lsvd disk image') p.add_argument('-c', '--cfg', help='Path to config file OR inline JSON string', required=False) p.set_defaults(func=bdev_lsvd_create) def bdev_lsvd_delete(args): print_json(args.client.call('bdev_lsvd_delete', { - 'name': args.name + 'image_name': args.image_name })) p = subparsers.add_parser('bdev_lsvd_delete', help='Delete a lsvd bdev') - p.add_argument('name', help='Name of the lsvd disk image') + p.add_argument('image_name', help='Name of the lsvd disk image') p.set_defaults(func=bdev_lsvd_delete) From 6f504b38577724d88085bebfb9cb87248619284c Mon Sep 17 00:00:00 2001 From: Isaac Khor Date: Fri, 9 Aug 2024 01:16:54 +0000 Subject: [PATCH 09/12] Fix deletion deadlock --- src/bdev_lsvd.cc | 24 ++++++++++++++---------- src/bdev_lsvd.h | 3 ++- src/bdev_lsvd_rpc.cc | 43 +++++++++++++++++++++++++------------------ 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/bdev_lsvd.cc b/src/bdev_lsvd.cc index 0fdc0a1b..661f7da1 100644 --- a/src/bdev_lsvd.cc +++ b/src/bdev_lsvd.cc @@ -228,19 +228,23 @@ int bdev_lsvd_create(str pool_name, str image_name, str user_cfg) return bdev_lsvd_create(image_name, be, cfg.value()); } -int bdev_lsvd_delete(str img_name) +void bdev_lsvd_delete(str img_name, std::function cb) { - // TODO this currently deadlocks because of spdk's threading model, need to - // make it async log_info("Deleting image '{}'", img_name); - auto p = std::promise(); - spdk_bdev_unregister_by_name( + auto rc = spdk_bdev_unregister_by_name( img_name.c_str(), &lsvd_if, + // some of the ugliest lifetime management code you'll ever see, but + // it should work [](void *arg, int rc) { - log_info("Image deletion complete, rc = {}", rc); - auto p = (std::promise *)arg; - p->set_value(rc); + log_info("Image deletion done, rc = {}", rc); + auto cb = (std::function *)arg; + (*cb)(rc); + delete cb; }, - &p); - return p.get_future().get(); + new std::function(cb)); + + if (rc != 0) { + log_error("Failed to delete image '{}': {}", img_name, rc); + cb(rc); + } } diff --git a/src/bdev_lsvd.h b/src/bdev_lsvd.h index d372a63c..3028494e 100644 --- a/src/bdev_lsvd.h +++ b/src/bdev_lsvd.h @@ -1,9 +1,10 @@ #pragma once #include +#include #include "config.h" int bdev_lsvd_create(str img_name, rados_ioctx_t io_ctx, lsvd_config cfg); int bdev_lsvd_create(str pool_name, str image_name, str cfg_path); -int bdev_lsvd_delete(str img_name); +void bdev_lsvd_delete(str img_name, std::function cb); diff --git a/src/bdev_lsvd_rpc.cc b/src/bdev_lsvd_rpc.cc index 7689b146..2f8484a9 100644 --- a/src/bdev_lsvd_rpc.cc +++ b/src/bdev_lsvd_rpc.cc @@ -22,9 +22,12 @@ struct rpc_create_lsvd { }; static const struct spdk_json_object_decoder rpc_create_lsvd_decoders[] = { - {"image_name", offsetof(rpc_create_lsvd, image_name), spdk_json_decode_string, false}, - {"pool_name", offsetof(rpc_create_lsvd, pool_name), spdk_json_decode_string, false}, - {"config", offsetof(rpc_create_lsvd, config), spdk_json_decode_string, true}, + {"image_name", offsetof(rpc_create_lsvd, image_name), + spdk_json_decode_string, false}, + {"pool_name", offsetof(rpc_create_lsvd, pool_name), spdk_json_decode_string, + false}, + {"config", offsetof(rpc_create_lsvd, config), spdk_json_decode_string, + true}, }; static void rpc_bdev_lsvd_create(spdk_jsonrpc_request *req_json, @@ -64,13 +67,13 @@ struct rpc_delete_lsvd { }; static const struct spdk_json_object_decoder rpc_delete_lsvd_decoders[] = { - {"image_name", offsetof(struct rpc_delete_lsvd, image_name), spdk_json_decode_string, false}, + {"image_name", offsetof(struct rpc_delete_lsvd, image_name), + spdk_json_decode_string, false}, }; static void rpc_bdev_lsvd_delete(struct spdk_jsonrpc_request *req_json, const struct spdk_json_val *params) { - spdk_json_write_ctx *w; std::unique_ptrimage_name); })> req(new rpc_delete_lsvd()); @@ -78,19 +81,23 @@ static void rpc_bdev_lsvd_delete(struct spdk_jsonrpc_request *req_json, int rc = spdk_json_decode_object(params, rpc_delete_lsvd_decoders, SPDK_COUNTOF(rpc_delete_lsvd_decoders), req.get()); - PR_GOTO_IF(rc != 0, fail, "failed to decode json object"); - - rc = bdev_lsvd_delete(req->image_name); - PR_GOTO_IF(rc != 0, fail, "failed to destroy lsvd bdev"); - - w = spdk_jsonrpc_begin_result(req_json); - PR_GOTO_IF(w == nullptr, fail, "failed to create json result"); - spdk_json_write_bool(w, true); - spdk_jsonrpc_end_result(req_json, w); - -fail: - spdk_jsonrpc_send_error_response(req_json, rc, - "failed to create lsvd bdev"); + if (rc != 0) { + spdk_jsonrpc_send_error_response(req_json, rc, + "Failed to parse rpc json"); + return; + } + + bdev_lsvd_delete(req->image_name, [=](int rc) { + if (rc == 0) { + auto w = spdk_jsonrpc_begin_result(req_json); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(req_json, w); + } else { + log_error("Failed to destroy lsvd bdev, rc = {}", rc); + spdk_jsonrpc_send_error_response(req_json, rc, + "Failed to destroy lsvd bdev"); + } + }); } SPDK_RPC_REGISTER("bdev_lsvd_delete", rpc_bdev_lsvd_delete, SPDK_RPC_RUNTIME) From db504ff3a893e609fa6fd3ef71218825f9b0edf2 Mon Sep 17 00:00:00 2001 From: Isaac Khor Date: Fri, 9 Aug 2024 01:20:05 +0000 Subject: [PATCH 10/12] Fix some ugly error handling --- src/bdev_lsvd_rpc.cc | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/bdev_lsvd_rpc.cc b/src/bdev_lsvd_rpc.cc index 2f8484a9..f9c80e67 100644 --- a/src/bdev_lsvd_rpc.cc +++ b/src/bdev_lsvd_rpc.cc @@ -33,7 +33,6 @@ static const struct spdk_json_object_decoder rpc_create_lsvd_decoders[] = { static void rpc_bdev_lsvd_create(spdk_jsonrpc_request *req_json, const spdk_json_val *params) { - spdk_json_write_ctx *w; std::unique_ptrimage_name); free(p->pool_name); @@ -44,20 +43,22 @@ static void rpc_bdev_lsvd_create(spdk_jsonrpc_request *req_json, auto rc = spdk_json_decode_object(params, rpc_create_lsvd_decoders, SPDK_COUNTOF(rpc_create_lsvd_decoders), req.get()); - PR_GOTO_IF(rc != 0, fail, "spdk_json_decode_object failed"); + if (rc != 0) { + spdk_jsonrpc_send_error_response(req_json, rc, + "Failed to parse rpc json"); + return; + } rc = bdev_lsvd_create(req->pool_name, req->image_name, req->config); - PR_GOTO_IF(rc != 0, fail, "failed to create lsvd bdev"); + if (rc != 0) { + spdk_jsonrpc_send_error_response(req_json, rc, + "Failed to create lsvd bdev"); + return; + } - w = spdk_jsonrpc_begin_result(req_json); - PR_GOTO_IF(w == nullptr, fail, "failed to create json result"); + auto w = spdk_jsonrpc_begin_result(req_json); spdk_json_write_bool(w, true); spdk_jsonrpc_end_result(req_json, w); - return; - -fail: - spdk_jsonrpc_send_error_response(req_json, rc, - "failed to create lsvd bdev"); } SPDK_RPC_REGISTER("bdev_lsvd_create", rpc_bdev_lsvd_create, SPDK_RPC_RUNTIME) @@ -67,7 +68,7 @@ struct rpc_delete_lsvd { }; static const struct spdk_json_object_decoder rpc_delete_lsvd_decoders[] = { - {"image_name", offsetof(struct rpc_delete_lsvd, image_name), + {"image_name", offsetof(rpc_delete_lsvd, image_name), spdk_json_decode_string, false}, }; From 54b21554a071bf562f9869908f5b01fd72ced0c0 Mon Sep 17 00:00:00 2001 From: Isaac Khor Date: Fri, 9 Aug 2024 01:36:11 +0000 Subject: [PATCH 11/12] Remove uneeded config_macros.h and add (and don't activate) bdev_rbd to spdk link flags --- src/config_macros.h | 94 ----------------------- subprojects/packagefiles/spdk/meson.build | 1 + 2 files changed, 1 insertion(+), 94 deletions(-) delete mode 100644 src/config_macros.h diff --git a/src/config_macros.h b/src/config_macros.h deleted file mode 100644 index 3b9eb88c..00000000 --- a/src/config_macros.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * file: config_macros.h - * description: really simple config file / env var parsing - * - * config file keyword = variable name - * environment variable = LSVD_(uppercase keyword) - * handles four types of values: - * - string - * - int - * - human-readable int (e.g. 10m, 20G) - * - table (actually std::map) lookup - * - * to use: - * F_CONFIG_XXX(input, arg, name) - if input==name, set name=arg - * ENV_CONFIG_XXX(name) - if LSVD_ is set, set name= - * - */ - -#include -#include -#include - -static long parseint(const char *_s) -{ - char *s = (char *)_s; - long val = strtol(s, &s, 0); - if (toupper(*s) == 'G') - val *= (1024 * 1024 * 1024); - if (toupper(*s) == 'M') - val *= (1024 * 1024); - if (toupper(*s) == 'K') - val *= 1024; - return val; -} - -static long parseint(std::string &s) { return parseint(s.c_str()); } - -#define CONFIG_HDR(name) \ - const char *val = NULL; \ - std::string env = "LSVD_" #name; \ - std::transform(env.begin(), env.end(), env.begin(), \ - [](unsigned char c) { return std::toupper(c); }); - -#define F_CONFIG_STR(input, arg, name) \ - { \ - if (input == #name) \ - name = arg; \ - } - -#define ENV_CONFIG_STR(name) \ - { \ - CONFIG_HDR(name) \ - if ((val = getenv(env.c_str()))) \ - name = std::string(val); \ - } - -#define F_CONFIG_INT(input, arg, name) \ - { \ - if (input == #name) \ - name = atoi(arg.c_str()); \ - } - -#define ENV_CONFIG_INT(name) \ - { \ - CONFIG_HDR(name) \ - if ((val = getenv(env.c_str()))) \ - name = atoi(val); \ - } - -#define F_CONFIG_H_INT(input, arg, name) \ - { \ - if (input == #name) \ - name = parseint(arg); \ - } - -#define ENV_CONFIG_H_INT(name) \ - { \ - CONFIG_HDR(name) \ - if ((val = getenv(env.c_str()))) \ - name = parseint(val); \ - } - -#define F_CONFIG_TABLE(input, arg, name, table) \ - { \ - if (input == #name) \ - name = table[arg]; \ - } - -#define ENV_CONFIG_TABLE(name, table) \ - { \ - CONFIG_HDR(name) \ - if ((val = getenv(env.c_str()))) \ - name = table[std::string(val)]; \ - } diff --git a/subprojects/packagefiles/spdk/meson.build b/subprojects/packagefiles/spdk/meson.build index c798d74c..8c57dbfe 100644 --- a/subprojects/packagefiles/spdk/meson.build +++ b/subprojects/packagefiles/spdk/meson.build @@ -50,6 +50,7 @@ custom_libnames = [ # 'spdk_bdev_split', # 'spdk_bdev_delay', # 'spdk_bdev_zone_block', + # 'spdk_bdev_rbd', 'spdk_blobfs_bdev', 'spdk_blobfs', 'spdk_blob_bdev', From 644e08ab62de2522ce7a1286bcd6dbb3d6ec7b03 Mon Sep 17 00:00:00 2001 From: Isaac Khor Date: Tue, 13 Aug 2024 02:42:25 +0000 Subject: [PATCH 12/12] Remove unused expected.hpp --- src/expected.hpp | 2441 ---------------------------------------------- 1 file changed, 2441 deletions(-) delete mode 100644 src/expected.hpp diff --git a/src/expected.hpp b/src/expected.hpp deleted file mode 100644 index 6d674daf..00000000 --- a/src/expected.hpp +++ /dev/null @@ -1,2441 +0,0 @@ -/// -// expected - An implementation of std::expected with extensions -// Written in 2017 by Sy Brand (tartanllama@gmail.com, @TartanLlama) -// -// Documentation available at http://tl.tartanllama.xyz/ -// -// To the extent possible under law, the author(s) have dedicated all -// copyright and related and neighboring rights to this software to the -// public domain worldwide. This software is distributed without any warranty. -// -// You should have received a copy of the CC0 Public Domain Dedication -// along with this software. If not, see -// . -/// - -#pragma once - -#define TL_EXPECTED_VERSION_MAJOR 1 -#define TL_EXPECTED_VERSION_MINOR 1 -#define TL_EXPECTED_VERSION_PATCH 0 - -#include -#include -#include -#include - -#if defined(__EXCEPTIONS) || defined(_CPPUNWIND) -#define TL_EXPECTED_EXCEPTIONS_ENABLED -#endif - -#if (defined(_MSC_VER) && _MSC_VER == 1900) -#define TL_EXPECTED_MSVC2015 -#define TL_EXPECTED_MSVC2015_CONSTEXPR -#else -#define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr -#endif - -#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ - !defined(__clang__)) -#define TL_EXPECTED_GCC49 -#endif - -#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ - !defined(__clang__)) -#define TL_EXPECTED_GCC54 -#endif - -#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ - !defined(__clang__)) -#define TL_EXPECTED_GCC55 -#endif - -#if !defined(TL_ASSERT) -//can't have assert in constexpr in C++11 and GCC 4.9 has a compiler bug -#if (__cplusplus > 201103L) && !defined(TL_EXPECTED_GCC49) -#include -#define TL_ASSERT(x) assert(x) -#else -#define TL_ASSERT(x) -#endif -#endif - -#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ - !defined(__clang__)) -// GCC < 5 doesn't support overloading on const&& for member functions - -#define TL_EXPECTED_NO_CONSTRR -// GCC < 5 doesn't support some standard C++11 type traits -#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ - std::has_trivial_copy_constructor -#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ - std::has_trivial_copy_assign - -// This one will be different for GCC 5.7 if it's ever supported -#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ - std::is_trivially_destructible - -// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks -// std::vector for non-copyable types -#elif (defined(__GNUC__) && __GNUC__ < 8 && !defined(__clang__)) -#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX -#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX -namespace tl { -namespace detail { -template -struct is_trivially_copy_constructible - : std::is_trivially_copy_constructible {}; -#ifdef _GLIBCXX_VECTOR -template -struct is_trivially_copy_constructible> : std::false_type {}; -#endif -} // namespace detail -} // namespace tl -#endif - -#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ - tl::detail::is_trivially_copy_constructible -#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ - std::is_trivially_copy_assignable -#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ - std::is_trivially_destructible -#else -#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ - std::is_trivially_copy_constructible -#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ - std::is_trivially_copy_assignable -#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ - std::is_trivially_destructible -#endif - -#if __cplusplus > 201103L -#define TL_EXPECTED_CXX14 -#endif - -#ifdef TL_EXPECTED_GCC49 -#define TL_EXPECTED_GCC49_CONSTEXPR -#else -#define TL_EXPECTED_GCC49_CONSTEXPR constexpr -#endif - -#if (__cplusplus == 201103L || defined(TL_EXPECTED_MSVC2015) || \ - defined(TL_EXPECTED_GCC49)) -#define TL_EXPECTED_11_CONSTEXPR -#else -#define TL_EXPECTED_11_CONSTEXPR constexpr -#endif - -namespace tl { -template class expected; - -#ifndef TL_MONOSTATE_INPLACE_MUTEX -#define TL_MONOSTATE_INPLACE_MUTEX -class monostate {}; - -struct in_place_t { - explicit in_place_t() = default; -}; -static constexpr in_place_t in_place{}; -#endif - -template class unexpected { -public: - static_assert(!std::is_same::value, "E must not be void"); - - unexpected() = delete; - constexpr explicit unexpected(const E &e) : m_val(e) {} - - constexpr explicit unexpected(E &&e) : m_val(std::move(e)) {} - - template ::value>::type * = nullptr> - constexpr explicit unexpected(Args &&...args) - : m_val(std::forward(args)...) {} - template < - class U, class... Args, - typename std::enable_if &, Args &&...>::value>::type * = nullptr> - constexpr explicit unexpected(std::initializer_list l, Args &&...args) - : m_val(l, std::forward(args)...) {} - - constexpr const E &value() const & { return m_val; } - TL_EXPECTED_11_CONSTEXPR E &value() & { return m_val; } - TL_EXPECTED_11_CONSTEXPR E &&value() && { return std::move(m_val); } - constexpr const E &&value() const && { return std::move(m_val); } - -private: - E m_val; -}; - -#ifdef __cpp_deduction_guides -template unexpected(E) -> unexpected; -#endif - -template -constexpr bool operator==(const unexpected &lhs, const unexpected &rhs) { - return lhs.value() == rhs.value(); -} -template -constexpr bool operator!=(const unexpected &lhs, const unexpected &rhs) { - return lhs.value() != rhs.value(); -} -template -constexpr bool operator<(const unexpected &lhs, const unexpected &rhs) { - return lhs.value() < rhs.value(); -} -template -constexpr bool operator<=(const unexpected &lhs, const unexpected &rhs) { - return lhs.value() <= rhs.value(); -} -template -constexpr bool operator>(const unexpected &lhs, const unexpected &rhs) { - return lhs.value() > rhs.value(); -} -template -constexpr bool operator>=(const unexpected &lhs, const unexpected &rhs) { - return lhs.value() >= rhs.value(); -} - -template -unexpected::type> make_unexpected(E &&e) { - return unexpected::type>(std::forward(e)); -} - -struct unexpect_t { - unexpect_t() = default; -}; -static constexpr unexpect_t unexpect{}; - -namespace detail { -template -[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) { -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - throw std::forward(e); -#else - (void)e; -#ifdef _MSC_VER - __assume(0); -#else - __builtin_unreachable(); -#endif -#endif -} - -#ifndef TL_TRAITS_MUTEX -#define TL_TRAITS_MUTEX -// C++14-style aliases for brevity -template using remove_const_t = typename std::remove_const::type; -template -using remove_reference_t = typename std::remove_reference::type; -template using decay_t = typename std::decay::type; -template -using enable_if_t = typename std::enable_if::type; -template -using conditional_t = typename std::conditional::type; - -// std::conjunction from C++17 -template struct conjunction : std::true_type {}; -template struct conjunction : B {}; -template -struct conjunction - : std::conditional, B>::type {}; - -#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L -#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND -#endif - -// In C++11 mode, there's an issue in libc++'s std::mem_fn -// which results in a hard-error when using it in a noexcept expression -// in some cases. This is a check to workaround the common failing case. -#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND -template -struct is_pointer_to_non_const_member_func : std::false_type {}; -template -struct is_pointer_to_non_const_member_func - : std::true_type {}; -template -struct is_pointer_to_non_const_member_func - : std::true_type {}; -template -struct is_pointer_to_non_const_member_func - : std::true_type {}; -template -struct is_pointer_to_non_const_member_func - : std::true_type {}; -template -struct is_pointer_to_non_const_member_func - : std::true_type {}; -template -struct is_pointer_to_non_const_member_func - : std::true_type {}; - -template struct is_const_or_const_ref : std::false_type {}; -template struct is_const_or_const_ref : std::true_type {}; -template struct is_const_or_const_ref : std::true_type {}; -#endif - -// std::invoke from C++17 -// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround -template < - typename Fn, typename... Args, -#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND - typename = enable_if_t::value && - is_const_or_const_ref::value)>, -#endif - typename = enable_if_t>::value>, int = 0> -constexpr auto invoke(Fn &&f, Args &&...args) noexcept( - noexcept(std::mem_fn(f)(std::forward(args)...))) - -> decltype(std::mem_fn(f)(std::forward(args)...)) { - return std::mem_fn(f)(std::forward(args)...); -} - -template >::value>> -constexpr auto invoke(Fn &&f, Args &&...args) noexcept( - noexcept(std::forward(f)(std::forward(args)...))) - -> decltype(std::forward(f)(std::forward(args)...)) { - return std::forward(f)(std::forward(args)...); -} - -// std::invoke_result from C++17 -template struct invoke_result_impl; - -template -struct invoke_result_impl< - F, - decltype(detail::invoke(std::declval(), std::declval()...), void()), - Us...> { - using type = - decltype(detail::invoke(std::declval(), std::declval()...)); -}; - -template -using invoke_result = invoke_result_impl; - -template -using invoke_result_t = typename invoke_result::type; - -#if defined(_MSC_VER) && _MSC_VER <= 1900 -// TODO make a version which works with MSVC 2015 -template struct is_swappable : std::true_type {}; - -template struct is_nothrow_swappable : std::true_type {}; -#else -// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept -namespace swap_adl_tests { -// if swap ADL finds this then it would call std::swap otherwise (same -// signature) -struct tag {}; - -template tag swap(T &, T &); -template tag swap(T (&a)[N], T (&b)[N]); - -// helper functions to test if an unqualified swap is possible, and if it -// becomes std::swap -template std::false_type can_swap(...) noexcept(false); -template (), std::declval()))> -std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), - std::declval()))); - -template std::false_type uses_std(...); -template -std::is_same(), std::declval())), tag> -uses_std(int); - -template -struct is_std_swap_noexcept - : std::integral_constant::value && - std::is_nothrow_move_assignable::value> {}; - -template -struct is_std_swap_noexcept : is_std_swap_noexcept {}; - -template -struct is_adl_swap_noexcept - : std::integral_constant(0))> {}; -} // namespace swap_adl_tests - -template -struct is_swappable - : std::integral_constant< - bool, - decltype(detail::swap_adl_tests::can_swap(0))::value && - (!decltype(detail::swap_adl_tests::uses_std(0))::value || - (std::is_move_assignable::value && - std::is_move_constructible::value))> {}; - -template -struct is_swappable - : std::integral_constant< - bool, - decltype(detail::swap_adl_tests::can_swap(0))::value && - (!decltype(detail::swap_adl_tests::uses_std( - 0))::value || - is_swappable::value)> {}; - -template -struct is_nothrow_swappable - : std::integral_constant< - bool, - is_swappable::value && - ((decltype(detail::swap_adl_tests::uses_std(0))::value && - detail::swap_adl_tests::is_std_swap_noexcept::value) || - (!decltype(detail::swap_adl_tests::uses_std(0))::value && - detail::swap_adl_tests::is_adl_swap_noexcept::value))> {}; -#endif -#endif - -// Trait for checking if a type is a tl::expected -template struct is_expected_impl : std::false_type {}; -template -struct is_expected_impl> : std::true_type {}; -template using is_expected = is_expected_impl>; - -template -using expected_enable_forward_value = detail::enable_if_t< - std::is_constructible::value && - !std::is_same, in_place_t>::value && - !std::is_same, detail::decay_t>::value && - !std::is_same, detail::decay_t>::value>; - -template -using expected_enable_from_other = detail::enable_if_t< - std::is_constructible::value && - std::is_constructible::value && - !std::is_constructible &>::value && - !std::is_constructible &&>::value && - !std::is_constructible &>::value && - !std::is_constructible &&>::value && - !std::is_convertible &, T>::value && - !std::is_convertible &&, T>::value && - !std::is_convertible &, T>::value && - !std::is_convertible &&, T>::value>; - -template -using is_void_or = conditional_t::value, std::true_type, U>; - -template -using is_copy_constructible_or_void = - is_void_or>; - -template -using is_move_constructible_or_void = - is_void_or>; - -template -using is_copy_assignable_or_void = is_void_or>; - -template -using is_move_assignable_or_void = is_void_or>; - -} // namespace detail - -namespace detail { -struct no_init_t {}; -static constexpr no_init_t no_init{}; - -// Implements the storage of the values, and ensures that the destructor is -// trivial if it can be. -// -// This specialization is for where neither `T` or `E` is trivially -// destructible, so the destructors must be called on destruction of the -// `expected` -template ::value, - bool = std::is_trivially_destructible::value> -struct expected_storage_base { - constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} - constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} - - template ::value> * = - nullptr> - constexpr expected_storage_base(in_place_t, Args &&...args) - : m_val(std::forward(args)...), m_has_val(true) {} - - template &, Args &&...>::value> * = nullptr> - constexpr expected_storage_base(in_place_t, std::initializer_list il, - Args &&...args) - : m_val(il, std::forward(args)...), m_has_val(true) {} - template ::value> * = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args &&...args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} - - template &, Args &&...>::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args &&...args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} - - ~expected_storage_base() { - if (m_has_val) { - m_val.~T(); - } else { - m_unexpect.~unexpected(); - } - } - union { - T m_val; - unexpected m_unexpect; - char m_no_init; - }; - bool m_has_val; -}; - -// This specialization is for when both `T` and `E` are trivially-destructible, -// so the destructor of the `expected` can be trivial. -template struct expected_storage_base { - constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} - constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} - - template ::value> * = - nullptr> - constexpr expected_storage_base(in_place_t, Args &&...args) - : m_val(std::forward(args)...), m_has_val(true) {} - - template &, Args &&...>::value> * = nullptr> - constexpr expected_storage_base(in_place_t, std::initializer_list il, - Args &&...args) - : m_val(il, std::forward(args)...), m_has_val(true) {} - template ::value> * = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args &&...args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} - - template &, Args &&...>::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args &&...args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} - - ~expected_storage_base() = default; - union { - T m_val; - unexpected m_unexpect; - char m_no_init; - }; - bool m_has_val; -}; - -// T is trivial, E is not. -template struct expected_storage_base { - constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} - TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t) - : m_no_init(), m_has_val(false) {} - - template ::value> * = - nullptr> - constexpr expected_storage_base(in_place_t, Args &&...args) - : m_val(std::forward(args)...), m_has_val(true) {} - - template &, Args &&...>::value> * = nullptr> - constexpr expected_storage_base(in_place_t, std::initializer_list il, - Args &&...args) - : m_val(il, std::forward(args)...), m_has_val(true) {} - template ::value> * = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args &&...args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} - - template &, Args &&...>::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args &&...args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} - - ~expected_storage_base() { - if (!m_has_val) { - m_unexpect.~unexpected(); - } - } - - union { - T m_val; - unexpected m_unexpect; - char m_no_init; - }; - bool m_has_val; -}; - -// E is trivial, T is not. -template struct expected_storage_base { - constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} - constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} - - template ::value> * = - nullptr> - constexpr expected_storage_base(in_place_t, Args &&...args) - : m_val(std::forward(args)...), m_has_val(true) {} - - template &, Args &&...>::value> * = nullptr> - constexpr expected_storage_base(in_place_t, std::initializer_list il, - Args &&...args) - : m_val(il, std::forward(args)...), m_has_val(true) {} - template ::value> * = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args &&...args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} - - template &, Args &&...>::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args &&...args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} - - ~expected_storage_base() { - if (m_has_val) { - m_val.~T(); - } - } - union { - T m_val; - unexpected m_unexpect; - char m_no_init; - }; - bool m_has_val; -}; - -// `T` is `void`, `E` is trivially-destructible -template struct expected_storage_base { - #if __GNUC__ <= 5 - //no constexpr for GCC 4/5 bug - #else - TL_EXPECTED_MSVC2015_CONSTEXPR - #endif - expected_storage_base() : m_has_val(true) {} - - constexpr expected_storage_base(no_init_t) : m_val(), m_has_val(false) {} - - constexpr expected_storage_base(in_place_t) : m_has_val(true) {} - - template ::value> * = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args &&...args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} - - template &, Args &&...>::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args &&...args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} - - ~expected_storage_base() = default; - struct dummy {}; - union { - unexpected m_unexpect; - dummy m_val; - }; - bool m_has_val; -}; - -// `T` is `void`, `E` is not trivially-destructible -template struct expected_storage_base { - constexpr expected_storage_base() : m_dummy(), m_has_val(true) {} - constexpr expected_storage_base(no_init_t) : m_dummy(), m_has_val(false) {} - - constexpr expected_storage_base(in_place_t) : m_dummy(), m_has_val(true) {} - - template ::value> * = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args &&...args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} - - template &, Args &&...>::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args &&...args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} - - ~expected_storage_base() { - if (!m_has_val) { - m_unexpect.~unexpected(); - } - } - - union { - unexpected m_unexpect; - char m_dummy; - }; - bool m_has_val; -}; - -// This base class provides some handy member functions which can be used in -// further derived classes -template -struct expected_operations_base : expected_storage_base { - using expected_storage_base::expected_storage_base; - - template void construct(Args &&...args) noexcept { - new (std::addressof(this->m_val)) T(std::forward(args)...); - this->m_has_val = true; - } - - template void construct_with(Rhs &&rhs) noexcept { - new (std::addressof(this->m_val)) T(std::forward(rhs).get()); - this->m_has_val = true; - } - - template void construct_error(Args &&...args) noexcept { - new (std::addressof(this->m_unexpect)) - unexpected(std::forward(args)...); - this->m_has_val = false; - } - -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - - // These assign overloads ensure that the most efficient assignment - // implementation is used while maintaining the strong exception guarantee. - // The problematic case is where rhs has a value, but *this does not. - // - // This overload handles the case where we can just copy-construct `T` - // directly into place without throwing. - template ::value> - * = nullptr> - void assign(const expected_operations_base &rhs) noexcept { - if (!this->m_has_val && rhs.m_has_val) { - geterr().~unexpected(); - construct(rhs.get()); - } else { - assign_common(rhs); - } - } - - // This overload handles the case where we can attempt to create a copy of - // `T`, then no-throw move it into place if the copy was successful. - template ::value && - std::is_nothrow_move_constructible::value> - * = nullptr> - void assign(const expected_operations_base &rhs) noexcept { - if (!this->m_has_val && rhs.m_has_val) { - T tmp = rhs.get(); - geterr().~unexpected(); - construct(std::move(tmp)); - } else { - assign_common(rhs); - } - } - - // This overload is the worst-case, where we have to move-construct the - // unexpected value into temporary storage, then try to copy the T into place. - // If the construction succeeds, then everything is fine, but if it throws, - // then we move the old unexpected value back into place before rethrowing the - // exception. - template ::value && - !std::is_nothrow_move_constructible::value> - * = nullptr> - void assign(const expected_operations_base &rhs) { - if (!this->m_has_val && rhs.m_has_val) { - auto tmp = std::move(geterr()); - geterr().~unexpected(); - -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - construct(rhs.get()); - } catch (...) { - geterr() = std::move(tmp); - throw; - } -#else - construct(rhs.get()); -#endif - } else { - assign_common(rhs); - } - } - - // These overloads do the same as above, but for rvalues - template ::value> - * = nullptr> - void assign(expected_operations_base &&rhs) noexcept { - if (!this->m_has_val && rhs.m_has_val) { - geterr().~unexpected(); - construct(std::move(rhs).get()); - } else { - assign_common(std::move(rhs)); - } - } - - template ::value> - * = nullptr> - void assign(expected_operations_base &&rhs) { - if (!this->m_has_val && rhs.m_has_val) { - auto tmp = std::move(geterr()); - geterr().~unexpected(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - construct(std::move(rhs).get()); - } catch (...) { - geterr() = std::move(tmp); - throw; - } -#else - construct(std::move(rhs).get()); -#endif - } else { - assign_common(std::move(rhs)); - } - } - -#else - - // If exceptions are disabled then we can just copy-construct - void assign(const expected_operations_base &rhs) noexcept { - if (!this->m_has_val && rhs.m_has_val) { - geterr().~unexpected(); - construct(rhs.get()); - } else { - assign_common(rhs); - } - } - - void assign(expected_operations_base &&rhs) noexcept { - if (!this->m_has_val && rhs.m_has_val) { - geterr().~unexpected(); - construct(std::move(rhs).get()); - } else { - assign_common(std::move(rhs)); - } - } - -#endif - - // The common part of move/copy assigning - template void assign_common(Rhs &&rhs) { - if (this->m_has_val) { - if (rhs.m_has_val) { - get() = std::forward(rhs).get(); - } else { - destroy_val(); - construct_error(std::forward(rhs).geterr()); - } - } else { - if (!rhs.m_has_val) { - geterr() = std::forward(rhs).geterr(); - } - } - } - - bool has_value() const { return this->m_has_val; } - - TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; } - constexpr const T &get() const & { return this->m_val; } - TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); } -#ifndef TL_EXPECTED_NO_CONSTRR - constexpr const T &&get() const && { return std::move(this->m_val); } -#endif - - TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { - return this->m_unexpect; - } - constexpr const unexpected &geterr() const & { return this->m_unexpect; } - TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { - return std::move(this->m_unexpect); - } -#ifndef TL_EXPECTED_NO_CONSTRR - constexpr const unexpected &&geterr() const && { - return std::move(this->m_unexpect); - } -#endif - - TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); } -}; - -// This base class provides some handy member functions which can be used in -// further derived classes -template -struct expected_operations_base : expected_storage_base { - using expected_storage_base::expected_storage_base; - - template void construct() noexcept { this->m_has_val = true; } - - // This function doesn't use its argument, but needs it so that code in - // levels above this can work independently of whether T is void - template void construct_with(Rhs &&) noexcept { - this->m_has_val = true; - } - - template void construct_error(Args &&...args) noexcept { - new (std::addressof(this->m_unexpect)) - unexpected(std::forward(args)...); - this->m_has_val = false; - } - - template void assign(Rhs &&rhs) noexcept { - if (!this->m_has_val) { - if (rhs.m_has_val) { - geterr().~unexpected(); - construct(); - } else { - geterr() = std::forward(rhs).geterr(); - } - } else { - if (!rhs.m_has_val) { - construct_error(std::forward(rhs).geterr()); - } - } - } - - bool has_value() const { return this->m_has_val; } - - TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { - return this->m_unexpect; - } - constexpr const unexpected &geterr() const & { return this->m_unexpect; } - TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { - return std::move(this->m_unexpect); - } -#ifndef TL_EXPECTED_NO_CONSTRR - constexpr const unexpected &&geterr() const && { - return std::move(this->m_unexpect); - } -#endif - - TL_EXPECTED_11_CONSTEXPR void destroy_val() { - // no-op - } -}; - -// This class manages conditionally having a trivial copy constructor -// This specialization is for when T and E are trivially copy constructible -template :: - value &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value> -struct expected_copy_base : expected_operations_base { - using expected_operations_base::expected_operations_base; -}; - -// This specialization is for when T or E are not trivially copy constructible -template -struct expected_copy_base : expected_operations_base { - using expected_operations_base::expected_operations_base; - - expected_copy_base() = default; - expected_copy_base(const expected_copy_base &rhs) - : expected_operations_base(no_init) { - if (rhs.has_value()) { - this->construct_with(rhs); - } else { - this->construct_error(rhs.geterr()); - } - } - - expected_copy_base(expected_copy_base &&rhs) = default; - expected_copy_base &operator=(const expected_copy_base &rhs) = default; - expected_copy_base &operator=(expected_copy_base &&rhs) = default; -}; - -// This class manages conditionally having a trivial move constructor -// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it -// doesn't implement an analogue to std::is_trivially_move_constructible. We -// have to make do with a non-trivial move constructor even if T is trivially -// move constructible -#ifndef TL_EXPECTED_GCC49 -template >::value - &&std::is_trivially_move_constructible::value> -struct expected_move_base : expected_copy_base { - using expected_copy_base::expected_copy_base; -}; -#else -template struct expected_move_base; -#endif -template -struct expected_move_base : expected_copy_base { - using expected_copy_base::expected_copy_base; - - expected_move_base() = default; - expected_move_base(const expected_move_base &rhs) = default; - - expected_move_base(expected_move_base &&rhs) noexcept( - std::is_nothrow_move_constructible::value) - : expected_copy_base(no_init) { - if (rhs.has_value()) { - this->construct_with(std::move(rhs)); - } else { - this->construct_error(std::move(rhs.geterr())); - } - } - expected_move_base &operator=(const expected_move_base &rhs) = default; - expected_move_base &operator=(expected_move_base &&rhs) = default; -}; - -// This class manages conditionally having a trivial copy assignment operator -template >::value - &&TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value - &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value - &&TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value> -struct expected_copy_assign_base : expected_move_base { - using expected_move_base::expected_move_base; -}; - -template -struct expected_copy_assign_base : expected_move_base { - using expected_move_base::expected_move_base; - - expected_copy_assign_base() = default; - expected_copy_assign_base(const expected_copy_assign_base &rhs) = default; - - expected_copy_assign_base(expected_copy_assign_base &&rhs) = default; - expected_copy_assign_base &operator=(const expected_copy_assign_base &rhs) { - this->assign(rhs); - return *this; - } - expected_copy_assign_base & - operator=(expected_copy_assign_base &&rhs) = default; -}; - -// This class manages conditionally having a trivial move assignment operator -// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it -// doesn't implement an analogue to std::is_trivially_move_assignable. We have -// to make do with a non-trivial move assignment operator even if T is trivially -// move assignable -#ifndef TL_EXPECTED_GCC49 -template , - std::is_trivially_move_constructible, - std::is_trivially_move_assignable>>:: - value &&std::is_trivially_destructible::value - &&std::is_trivially_move_constructible::value - &&std::is_trivially_move_assignable::value> -struct expected_move_assign_base : expected_copy_assign_base { - using expected_copy_assign_base::expected_copy_assign_base; -}; -#else -template struct expected_move_assign_base; -#endif - -template -struct expected_move_assign_base - : expected_copy_assign_base { - using expected_copy_assign_base::expected_copy_assign_base; - - expected_move_assign_base() = default; - expected_move_assign_base(const expected_move_assign_base &rhs) = default; - - expected_move_assign_base(expected_move_assign_base &&rhs) = default; - - expected_move_assign_base & - operator=(const expected_move_assign_base &rhs) = default; - - expected_move_assign_base & - operator=(expected_move_assign_base &&rhs) noexcept( - std::is_nothrow_move_constructible::value - &&std::is_nothrow_move_assignable::value) { - this->assign(std::move(rhs)); - return *this; - } -}; - -// expected_delete_ctor_base will conditionally delete copy and move -// constructors depending on whether T is copy/move constructible -template ::value && - std::is_copy_constructible::value), - bool EnableMove = (is_move_constructible_or_void::value && - std::is_move_constructible::value)> -struct expected_delete_ctor_base { - expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base &) = default; - expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; - expected_delete_ctor_base & - operator=(const expected_delete_ctor_base &) = default; - expected_delete_ctor_base & - operator=(expected_delete_ctor_base &&) noexcept = default; -}; - -template -struct expected_delete_ctor_base { - expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base &) = default; - expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; - expected_delete_ctor_base & - operator=(const expected_delete_ctor_base &) = default; - expected_delete_ctor_base & - operator=(expected_delete_ctor_base &&) noexcept = default; -}; - -template -struct expected_delete_ctor_base { - expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; - expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; - expected_delete_ctor_base & - operator=(const expected_delete_ctor_base &) = default; - expected_delete_ctor_base & - operator=(expected_delete_ctor_base &&) noexcept = default; -}; - -template -struct expected_delete_ctor_base { - expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; - expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; - expected_delete_ctor_base & - operator=(const expected_delete_ctor_base &) = default; - expected_delete_ctor_base & - operator=(expected_delete_ctor_base &&) noexcept = default; -}; - -// expected_delete_assign_base will conditionally delete copy and move -// constructors depending on whether T and E are copy/move constructible + -// assignable -template ::value && - std::is_copy_constructible::value && - is_copy_assignable_or_void::value && - std::is_copy_assignable::value), - bool EnableMove = (is_move_constructible_or_void::value && - std::is_move_constructible::value && - is_move_assignable_or_void::value && - std::is_move_assignable::value)> -struct expected_delete_assign_base { - expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base &) = default; - expected_delete_assign_base(expected_delete_assign_base &&) noexcept = - default; - expected_delete_assign_base & - operator=(const expected_delete_assign_base &) = default; - expected_delete_assign_base & - operator=(expected_delete_assign_base &&) noexcept = default; -}; - -template -struct expected_delete_assign_base { - expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base &) = default; - expected_delete_assign_base(expected_delete_assign_base &&) noexcept = - default; - expected_delete_assign_base & - operator=(const expected_delete_assign_base &) = default; - expected_delete_assign_base & - operator=(expected_delete_assign_base &&) noexcept = delete; -}; - -template -struct expected_delete_assign_base { - expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base &) = default; - expected_delete_assign_base(expected_delete_assign_base &&) noexcept = - default; - expected_delete_assign_base & - operator=(const expected_delete_assign_base &) = delete; - expected_delete_assign_base & - operator=(expected_delete_assign_base &&) noexcept = default; -}; - -template -struct expected_delete_assign_base { - expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base &) = default; - expected_delete_assign_base(expected_delete_assign_base &&) noexcept = - default; - expected_delete_assign_base & - operator=(const expected_delete_assign_base &) = delete; - expected_delete_assign_base & - operator=(expected_delete_assign_base &&) noexcept = delete; -}; - -// This is needed to be able to construct the expected_default_ctor_base which -// follows, while still conditionally deleting the default constructor. -struct default_constructor_tag { - explicit constexpr default_constructor_tag() = default; -}; - -// expected_default_ctor_base will ensure that expected has a deleted default -// consturctor if T is not default constructible. -// This specialization is for when T is default constructible -template ::value || std::is_void::value> -struct expected_default_ctor_base { - constexpr expected_default_ctor_base() noexcept = default; - constexpr expected_default_ctor_base( - expected_default_ctor_base const &) noexcept = default; - constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = - default; - expected_default_ctor_base & - operator=(expected_default_ctor_base const &) noexcept = default; - expected_default_ctor_base & - operator=(expected_default_ctor_base &&) noexcept = default; - - constexpr explicit expected_default_ctor_base(default_constructor_tag) {} -}; - -// This specialization is for when T is not default constructible -template struct expected_default_ctor_base { - constexpr expected_default_ctor_base() noexcept = delete; - constexpr expected_default_ctor_base( - expected_default_ctor_base const &) noexcept = default; - constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = - default; - expected_default_ctor_base & - operator=(expected_default_ctor_base const &) noexcept = default; - expected_default_ctor_base & - operator=(expected_default_ctor_base &&) noexcept = default; - - constexpr explicit expected_default_ctor_base(default_constructor_tag) {} -}; -} // namespace detail - -template class bad_expected_access : public std::exception { -public: - explicit bad_expected_access(E e) : m_val(std::move(e)) {} - - virtual const char *what() const noexcept override { - return "Bad expected access"; - } - - const E &error() const & { return m_val; } - E &error() & { return m_val; } - const E &&error() const && { return std::move(m_val); } - E &&error() && { return std::move(m_val); } - -private: - E m_val; -}; - -/// An `expected` object is an object that contains the storage for -/// another object and manages the lifetime of this contained object `T`. -/// Alternatively it could contain the storage for another unexpected object -/// `E`. The contained object may not be initialized after the expected object -/// has been initialized, and may not be destroyed before the expected object -/// has been destroyed. The initialization state of the contained object is -/// tracked by the expected object. -template -class expected : private detail::expected_move_assign_base, - private detail::expected_delete_ctor_base, - private detail::expected_delete_assign_base, - private detail::expected_default_ctor_base { - static_assert(!std::is_reference::value, "T must not be a reference"); - static_assert(!std::is_same::type>::value, - "T must not be in_place_t"); - static_assert(!std::is_same::type>::value, - "T must not be unexpect_t"); - static_assert( - !std::is_same>::type>::value, - "T must not be unexpected"); - static_assert(!std::is_reference::value, "E must not be a reference"); - - T *valptr() { return std::addressof(this->m_val); } - const T *valptr() const { return std::addressof(this->m_val); } - unexpected *errptr() { return std::addressof(this->m_unexpect); } - const unexpected *errptr() const { - return std::addressof(this->m_unexpect); - } - - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &val() { - return this->m_val; - } - TL_EXPECTED_11_CONSTEXPR unexpected &err() { return this->m_unexpect; } - - template ::value> * = nullptr> - constexpr const U &val() const { - return this->m_val; - } - constexpr const unexpected &err() const { return this->m_unexpect; } - - using impl_base = detail::expected_move_assign_base; - using ctor_base = detail::expected_default_ctor_base; - -public: - typedef T value_type; - typedef E error_type; - typedef unexpected unexpected_type; - -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { - return and_then_impl(*this, std::forward(f)); - } - template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { - return and_then_impl(std::move(*this), std::forward(f)); - } - template constexpr auto and_then(F &&f) const & { - return and_then_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template constexpr auto and_then(F &&f) const && { - return and_then_impl(std::move(*this), std::forward(f)); - } -#endif - -#else - template - TL_EXPECTED_11_CONSTEXPR auto - and_then(F &&f) & -> decltype(and_then_impl(std::declval(), - std::forward(f))) { - return and_then_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR auto - and_then(F &&f) && -> decltype(and_then_impl(std::declval(), - std::forward(f))) { - return and_then_impl(std::move(*this), std::forward(f)); - } - template - constexpr auto and_then(F &&f) const & -> decltype(and_then_impl( - std::declval(), std::forward(f))) { - return and_then_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr auto and_then(F &&f) const && -> decltype(and_then_impl( - std::declval(), std::forward(f))) { - return and_then_impl(std::move(*this), std::forward(f)); - } -#endif -#endif - -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & { - return expected_map_impl(*this, std::forward(f)); - } - template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && { - return expected_map_impl(std::move(*this), std::forward(f)); - } - template constexpr auto map(F &&f) const & { - return expected_map_impl(*this, std::forward(f)); - } - template constexpr auto map(F &&f) const && { - return expected_map_impl(std::move(*this), std::forward(f)); - } -#else - template - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( - std::declval(), std::declval())) - map(F &&f) & { - return expected_map_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), - std::declval())) - map(F &&f) && { - return expected_map_impl(std::move(*this), std::forward(f)); - } - template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - map(F &&f) const & { - return expected_map_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - map(F &&f) const && { - return expected_map_impl(std::move(*this), std::forward(f)); - } -#endif -#endif - -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & { - return expected_map_impl(*this, std::forward(f)); - } - template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && { - return expected_map_impl(std::move(*this), std::forward(f)); - } - template constexpr auto transform(F &&f) const & { - return expected_map_impl(*this, std::forward(f)); - } - template constexpr auto transform(F &&f) const && { - return expected_map_impl(std::move(*this), std::forward(f)); - } -#else - template - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( - std::declval(), std::declval())) - transform(F &&f) & { - return expected_map_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), - std::declval())) - transform(F &&f) && { - return expected_map_impl(std::move(*this), std::forward(f)); - } - template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - transform(F &&f) const & { - return expected_map_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - transform(F &&f) const && { - return expected_map_impl(std::move(*this), std::forward(f)); - } -#endif -#endif - -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & { - return map_error_impl(*this, std::forward(f)); - } - template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && { - return map_error_impl(std::move(*this), std::forward(f)); - } - template constexpr auto map_error(F &&f) const & { - return map_error_impl(*this, std::forward(f)); - } - template constexpr auto map_error(F &&f) const && { - return map_error_impl(std::move(*this), std::forward(f)); - } -#else - template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F &&f) & { - return map_error_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F &&f) && { - return map_error_impl(std::move(*this), std::forward(f)); - } - template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F &&f) const & { - return map_error_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F &&f) const && { - return map_error_impl(std::move(*this), std::forward(f)); - } -#endif -#endif -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) & { - return map_error_impl(*this, std::forward(f)); - } - template TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) && { - return map_error_impl(std::move(*this), std::forward(f)); - } - template constexpr auto transform_error(F &&f) const & { - return map_error_impl(*this, std::forward(f)); - } - template constexpr auto transform_error(F &&f) const && { - return map_error_impl(std::move(*this), std::forward(f)); - } -#else - template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), - std::declval())) - transform_error(F &&f) & { - return map_error_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), - std::declval())) - transform_error(F &&f) && { - return map_error_impl(std::move(*this), std::forward(f)); - } - template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) - transform_error(F &&f) const & { - return map_error_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) - transform_error(F &&f) const && { - return map_error_impl(std::move(*this), std::forward(f)); - } -#endif -#endif - template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & { - return or_else_impl(*this, std::forward(f)); - } - - template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && { - return or_else_impl(std::move(*this), std::forward(f)); - } - - template expected constexpr or_else(F &&f) const & { - return or_else_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template expected constexpr or_else(F &&f) const && { - return or_else_impl(std::move(*this), std::forward(f)); - } -#endif - constexpr expected() = default; - constexpr expected(const expected &rhs) = default; - constexpr expected(expected &&rhs) = default; - expected &operator=(const expected &rhs) = default; - expected &operator=(expected &&rhs) = default; - - template ::value> * = - nullptr> - constexpr expected(in_place_t, Args &&...args) - : impl_base(in_place, std::forward(args)...), - ctor_base(detail::default_constructor_tag{}) {} - - template &, Args &&...>::value> * = nullptr> - constexpr expected(in_place_t, std::initializer_list il, Args &&...args) - : impl_base(in_place, il, std::forward(args)...), - ctor_base(detail::default_constructor_tag{}) {} - - template ::value> * = - nullptr, - detail::enable_if_t::value> * = - nullptr> - explicit constexpr expected(const unexpected &e) - : impl_base(unexpect, e.value()), - ctor_base(detail::default_constructor_tag{}) {} - - template < - class G = E, - detail::enable_if_t::value> * = - nullptr, - detail::enable_if_t::value> * = nullptr> - constexpr expected(unexpected const &e) - : impl_base(unexpect, e.value()), - ctor_base(detail::default_constructor_tag{}) {} - - template < - class G = E, - detail::enable_if_t::value> * = nullptr, - detail::enable_if_t::value> * = nullptr> - explicit constexpr expected(unexpected &&e) noexcept( - std::is_nothrow_constructible::value) - : impl_base(unexpect, std::move(e.value())), - ctor_base(detail::default_constructor_tag{}) {} - - template < - class G = E, - detail::enable_if_t::value> * = nullptr, - detail::enable_if_t::value> * = nullptr> - constexpr expected(unexpected &&e) noexcept( - std::is_nothrow_constructible::value) - : impl_base(unexpect, std::move(e.value())), - ctor_base(detail::default_constructor_tag{}) {} - - template ::value> * = - nullptr> - constexpr explicit expected(unexpect_t, Args &&...args) - : impl_base(unexpect, std::forward(args)...), - ctor_base(detail::default_constructor_tag{}) {} - - template &, Args &&...>::value> * = nullptr> - constexpr explicit expected(unexpect_t, std::initializer_list il, - Args &&...args) - : impl_base(unexpect, il, std::forward(args)...), - ctor_base(detail::default_constructor_tag{}) {} - - template ::value && - std::is_convertible::value)> * = - nullptr, - detail::expected_enable_from_other - * = nullptr> - explicit TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) - : ctor_base(detail::default_constructor_tag{}) { - if (rhs.has_value()) { - this->construct(*rhs); - } else { - this->construct_error(rhs.error()); - } - } - - template ::value && - std::is_convertible::value)> * = - nullptr, - detail::expected_enable_from_other - * = nullptr> - TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) - : ctor_base(detail::default_constructor_tag{}) { - if (rhs.has_value()) { - this->construct(*rhs); - } else { - this->construct_error(rhs.error()); - } - } - - template < - class U, class G, - detail::enable_if_t::value && - std::is_convertible::value)> * = nullptr, - detail::expected_enable_from_other * = nullptr> - explicit TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) - : ctor_base(detail::default_constructor_tag{}) { - if (rhs.has_value()) { - this->construct(std::move(*rhs)); - } else { - this->construct_error(std::move(rhs.error())); - } - } - - template < - class U, class G, - detail::enable_if_t<(std::is_convertible::value && - std::is_convertible::value)> * = nullptr, - detail::expected_enable_from_other * = nullptr> - TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) - : ctor_base(detail::default_constructor_tag{}) { - if (rhs.has_value()) { - this->construct(std::move(*rhs)); - } else { - this->construct_error(std::move(rhs.error())); - } - } - - template < - class U = T, - detail::enable_if_t::value> * = nullptr, - detail::expected_enable_forward_value * = nullptr> - explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) - : expected(in_place, std::forward(v)) {} - - template < - class U = T, - detail::enable_if_t::value> * = nullptr, - detail::expected_enable_forward_value * = nullptr> - TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) - : expected(in_place, std::forward(v)) {} - - template < - class U = T, class G = T, - detail::enable_if_t::value> * = - nullptr, - detail::enable_if_t::value> * = nullptr, - detail::enable_if_t< - (!std::is_same, detail::decay_t>::value && - !detail::conjunction, - std::is_same>>::value && - std::is_constructible::value && - std::is_assignable::value && - std::is_nothrow_move_constructible::value)> * = nullptr> - expected &operator=(U &&v) { - if (has_value()) { - val() = std::forward(v); - } else { - err().~unexpected(); - ::new (valptr()) T(std::forward(v)); - this->m_has_val = true; - } - - return *this; - } - - template < - class U = T, class G = T, - detail::enable_if_t::value> * = - nullptr, - detail::enable_if_t::value> * = nullptr, - detail::enable_if_t< - (!std::is_same, detail::decay_t>::value && - !detail::conjunction, - std::is_same>>::value && - std::is_constructible::value && - std::is_assignable::value && - std::is_nothrow_move_constructible::value)> * = nullptr> - expected &operator=(U &&v) { - if (has_value()) { - val() = std::forward(v); - } else { - auto tmp = std::move(err()); - err().~unexpected(); - -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (valptr()) T(std::forward(v)); - this->m_has_val = true; - } catch (...) { - err() = std::move(tmp); - throw; - } -#else - ::new (valptr()) T(std::forward(v)); - this->m_has_val = true; -#endif - } - - return *this; - } - - template ::value && - std::is_assignable::value> * = nullptr> - expected &operator=(const unexpected &rhs) { - if (!has_value()) { - err() = rhs; - } else { - this->destroy_val(); - ::new (errptr()) unexpected(rhs); - this->m_has_val = false; - } - - return *this; - } - - template ::value && - std::is_move_assignable::value> * = nullptr> - expected &operator=(unexpected &&rhs) noexcept { - if (!has_value()) { - err() = std::move(rhs); - } else { - this->destroy_val(); - ::new (errptr()) unexpected(std::move(rhs)); - this->m_has_val = false; - } - - return *this; - } - - template ::value> * = nullptr> - void emplace(Args &&...args) { - if (has_value()) { - val().~T(); - } else { - err().~unexpected(); - this->m_has_val = true; - } - ::new (valptr()) T(std::forward(args)...); - } - - template ::value> * = nullptr> - void emplace(Args &&...args) { - if (has_value()) { - val().~T(); - ::new (valptr()) T(std::forward(args)...); - } else { - auto tmp = std::move(err()); - err().~unexpected(); - -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (valptr()) T(std::forward(args)...); - this->m_has_val = true; - } catch (...) { - err() = std::move(tmp); - throw; - } -#else - ::new (valptr()) T(std::forward(args)...); - this->m_has_val = true; -#endif - } - } - - template &, Args &&...>::value> * = nullptr> - void emplace(std::initializer_list il, Args &&...args) { - if (has_value()) { - T t(il, std::forward(args)...); - val() = std::move(t); - } else { - err().~unexpected(); - ::new (valptr()) T(il, std::forward(args)...); - this->m_has_val = true; - } - } - - template &, Args &&...>::value> * = nullptr> - void emplace(std::initializer_list il, Args &&...args) { - if (has_value()) { - T t(il, std::forward(args)...); - val() = std::move(t); - } else { - auto tmp = std::move(err()); - err().~unexpected(); - -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (valptr()) T(il, std::forward(args)...); - this->m_has_val = true; - } catch (...) { - err() = std::move(tmp); - throw; - } -#else - ::new (valptr()) T(il, std::forward(args)...); - this->m_has_val = true; -#endif - } - } - -private: - using t_is_void = std::true_type; - using t_is_not_void = std::false_type; - using t_is_nothrow_move_constructible = std::true_type; - using move_constructing_t_can_throw = std::false_type; - using e_is_nothrow_move_constructible = std::true_type; - using move_constructing_e_can_throw = std::false_type; - - void swap_where_both_have_value(expected & /*rhs*/, t_is_void) noexcept { - // swapping void is a no-op - } - - void swap_where_both_have_value(expected &rhs, t_is_not_void) { - using std::swap; - swap(val(), rhs.val()); - } - - void swap_where_only_one_has_value(expected &rhs, t_is_void) noexcept( - std::is_nothrow_move_constructible::value) { - ::new (errptr()) unexpected_type(std::move(rhs.err())); - rhs.err().~unexpected_type(); - std::swap(this->m_has_val, rhs.m_has_val); - } - - void swap_where_only_one_has_value(expected &rhs, t_is_not_void) { - swap_where_only_one_has_value_and_t_is_not_void( - rhs, typename std::is_nothrow_move_constructible::type{}, - typename std::is_nothrow_move_constructible::type{}); - } - - void swap_where_only_one_has_value_and_t_is_not_void( - expected &rhs, t_is_nothrow_move_constructible, - e_is_nothrow_move_constructible) noexcept { - auto temp = std::move(val()); - val().~T(); - ::new (errptr()) unexpected_type(std::move(rhs.err())); - rhs.err().~unexpected_type(); - ::new (rhs.valptr()) T(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); - } - - void swap_where_only_one_has_value_and_t_is_not_void( - expected &rhs, t_is_nothrow_move_constructible, - move_constructing_e_can_throw) { - auto temp = std::move(val()); - val().~T(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (errptr()) unexpected_type(std::move(rhs.err())); - rhs.err().~unexpected_type(); - ::new (rhs.valptr()) T(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); - } catch (...) { - val() = std::move(temp); - throw; - } -#else - ::new (errptr()) unexpected_type(std::move(rhs.err())); - rhs.err().~unexpected_type(); - ::new (rhs.valptr()) T(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); -#endif - } - - void swap_where_only_one_has_value_and_t_is_not_void( - expected &rhs, move_constructing_t_can_throw, - e_is_nothrow_move_constructible) { - auto temp = std::move(rhs.err()); - rhs.err().~unexpected_type(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (rhs.valptr()) T(std::move(val())); - val().~T(); - ::new (errptr()) unexpected_type(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); - } catch (...) { - rhs.err() = std::move(temp); - throw; - } -#else - ::new (rhs.valptr()) T(std::move(val())); - val().~T(); - ::new (errptr()) unexpected_type(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); -#endif - } - -public: - template - detail::enable_if_t::value && - detail::is_swappable::value && - (std::is_nothrow_move_constructible::value || - std::is_nothrow_move_constructible::value)> - swap(expected &rhs) noexcept( - std::is_nothrow_move_constructible::value - &&detail::is_nothrow_swappable::value - &&std::is_nothrow_move_constructible::value - &&detail::is_nothrow_swappable::value) { - if (has_value() && rhs.has_value()) { - swap_where_both_have_value(rhs, typename std::is_void::type{}); - } else if (!has_value() && rhs.has_value()) { - rhs.swap(*this); - } else if (has_value()) { - swap_where_only_one_has_value(rhs, typename std::is_void::type{}); - } else { - using std::swap; - swap(err(), rhs.err()); - } - } - - constexpr const T *operator->() const { - TL_ASSERT(has_value()); - return valptr(); - } - TL_EXPECTED_11_CONSTEXPR T *operator->() { - TL_ASSERT(has_value()); - return valptr(); - } - - template ::value> * = nullptr> - constexpr const U &operator*() const & { - TL_ASSERT(has_value()); - return val(); - } - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &operator*() & { - TL_ASSERT(has_value()); - return val(); - } - template ::value> * = nullptr> - constexpr const U &&operator*() const && { - TL_ASSERT(has_value()); - return std::move(val()); - } - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &&operator*() && { - TL_ASSERT(has_value()); - return std::move(val()); - } - - constexpr bool has_value() const noexcept { return this->m_has_val; } - constexpr explicit operator bool() const noexcept { return this->m_has_val; } - - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR const U &value() const & { - if (!has_value()) - detail::throw_exception(bad_expected_access(err().value())); - return val(); - } - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &value() & { - if (!has_value()) - detail::throw_exception(bad_expected_access(err().value())); - return val(); - } - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR const U &&value() const && { - if (!has_value()) - detail::throw_exception(bad_expected_access(std::move(err()).value())); - return std::move(val()); - } - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &&value() && { - if (!has_value()) - detail::throw_exception(bad_expected_access(std::move(err()).value())); - return std::move(val()); - } - - constexpr const E &error() const & { - TL_ASSERT(!has_value()); - return err().value(); - } - TL_EXPECTED_11_CONSTEXPR E &error() & { - TL_ASSERT(!has_value()); - return err().value(); - } - constexpr const E &&error() const && { - TL_ASSERT(!has_value()); - return std::move(err().value()); - } - TL_EXPECTED_11_CONSTEXPR E &&error() && { - TL_ASSERT(!has_value()); - return std::move(err().value()); - } - - template constexpr T value_or(U &&v) const & { - static_assert(std::is_copy_constructible::value && - std::is_convertible::value, - "T must be copy-constructible and convertible to from U&&"); - return bool(*this) ? **this : static_cast(std::forward(v)); - } - template TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { - static_assert(std::is_move_constructible::value && - std::is_convertible::value, - "T must be move-constructible and convertible to from U&&"); - return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); - } -}; - -namespace detail { -template using exp_t = typename detail::decay_t::value_type; -template using err_t = typename detail::decay_t::error_type; -template using ret_t = expected>; - -#ifdef TL_EXPECTED_CXX14 -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval()))> -constexpr auto and_then_impl(Exp &&exp, F &&f) { - static_assert(detail::is_expected::value, "F must return an expected"); - - return exp.has_value() - ? detail::invoke(std::forward(f), *std::forward(exp)) - : Ret(unexpect, std::forward(exp).error()); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval()))> -constexpr auto and_then_impl(Exp &&exp, F &&f) { - static_assert(detail::is_expected::value, "F must return an expected"); - - return exp.has_value() ? detail::invoke(std::forward(f)) - : Ret(unexpect, std::forward(exp).error()); -} -#else -template struct TC; -template (), - *std::declval())), - detail::enable_if_t>::value> * = nullptr> -auto and_then_impl(Exp &&exp, F &&f) -> Ret { - static_assert(detail::is_expected::value, "F must return an expected"); - - return exp.has_value() - ? detail::invoke(std::forward(f), *std::forward(exp)) - : Ret(unexpect, std::forward(exp).error()); -} - -template ())), - detail::enable_if_t>::value> * = nullptr> -constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { - static_assert(detail::is_expected::value, "F must return an expected"); - - return exp.has_value() ? detail::invoke(std::forward(f)) - : Ret(unexpect, std::forward(exp).error()); -} -#endif - -#ifdef TL_EXPECTED_CXX14 -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value> * = nullptr> -constexpr auto expected_map_impl(Exp &&exp, F &&f) { - using result = ret_t>; - return exp.has_value() ? result(detail::invoke(std::forward(f), - *std::forward(exp))) - : result(unexpect, std::forward(exp).error()); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value> * = nullptr> -auto expected_map_impl(Exp &&exp, F &&f) { - using result = expected>; - if (exp.has_value()) { - detail::invoke(std::forward(f), *std::forward(exp)); - return result(); - } - - return result(unexpect, std::forward(exp).error()); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value> * = nullptr> -constexpr auto expected_map_impl(Exp &&exp, F &&f) { - using result = ret_t>; - return exp.has_value() ? result(detail::invoke(std::forward(f))) - : result(unexpect, std::forward(exp).error()); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value> * = nullptr> -auto expected_map_impl(Exp &&exp, F &&f) { - using result = expected>; - if (exp.has_value()) { - detail::invoke(std::forward(f)); - return result(); - } - - return result(unexpect, std::forward(exp).error()); -} -#else -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value> * = nullptr> - -constexpr auto expected_map_impl(Exp &&exp, F &&f) - -> ret_t> { - using result = ret_t>; - - return exp.has_value() ? result(detail::invoke(std::forward(f), - *std::forward(exp))) - : result(unexpect, std::forward(exp).error()); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value> * = nullptr> - -auto expected_map_impl(Exp &&exp, F &&f) -> expected> { - if (exp.has_value()) { - detail::invoke(std::forward(f), *std::forward(exp)); - return {}; - } - - return unexpected>(std::forward(exp).error()); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value> * = nullptr> - -constexpr auto expected_map_impl(Exp &&exp, F &&f) - -> ret_t> { - using result = ret_t>; - - return exp.has_value() ? result(detail::invoke(std::forward(f))) - : result(unexpect, std::forward(exp).error()); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value> * = nullptr> - -auto expected_map_impl(Exp &&exp, F &&f) -> expected> { - if (exp.has_value()) { - detail::invoke(std::forward(f)); - return {}; - } - - return unexpected>(std::forward(exp).error()); -} -#endif - -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -constexpr auto map_error_impl(Exp &&exp, F &&f) { - using result = expected, detail::decay_t>; - return exp.has_value() - ? result(*std::forward(exp)) - : result(unexpect, detail::invoke(std::forward(f), - std::forward(exp).error())); -} -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -auto map_error_impl(Exp &&exp, F &&f) { - using result = expected, monostate>; - if (exp.has_value()) { - return result(*std::forward(exp)); - } - - detail::invoke(std::forward(f), std::forward(exp).error()); - return result(unexpect, monostate{}); -} -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -constexpr auto map_error_impl(Exp &&exp, F &&f) { - using result = expected, detail::decay_t>; - return exp.has_value() - ? result() - : result(unexpect, detail::invoke(std::forward(f), - std::forward(exp).error())); -} -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -auto map_error_impl(Exp &&exp, F &&f) { - using result = expected, monostate>; - if (exp.has_value()) { - return result(); - } - - detail::invoke(std::forward(f), std::forward(exp).error()); - return result(unexpect, monostate{}); -} -#else -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -constexpr auto map_error_impl(Exp &&exp, F &&f) - -> expected, detail::decay_t> { - using result = expected, detail::decay_t>; - - return exp.has_value() - ? result(*std::forward(exp)) - : result(unexpect, detail::invoke(std::forward(f), - std::forward(exp).error())); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { - using result = expected, monostate>; - if (exp.has_value()) { - return result(*std::forward(exp)); - } - - detail::invoke(std::forward(f), std::forward(exp).error()); - return result(unexpect, monostate{}); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -constexpr auto map_error_impl(Exp &&exp, F &&f) - -> expected, detail::decay_t> { - using result = expected, detail::decay_t>; - - return exp.has_value() - ? result() - : result(unexpect, detail::invoke(std::forward(f), - std::forward(exp).error())); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { - using result = expected, monostate>; - if (exp.has_value()) { - return result(); - } - - detail::invoke(std::forward(f), std::forward(exp).error()); - return result(unexpect, monostate{}); -} -#endif - -#ifdef TL_EXPECTED_CXX14 -template (), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -constexpr auto or_else_impl(Exp &&exp, F &&f) { - static_assert(detail::is_expected::value, "F must return an expected"); - return exp.has_value() ? std::forward(exp) - : detail::invoke(std::forward(f), - std::forward(exp).error()); -} - -template (), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -detail::decay_t or_else_impl(Exp &&exp, F &&f) { - return exp.has_value() ? std::forward(exp) - : (detail::invoke(std::forward(f), - std::forward(exp).error()), - std::forward(exp)); -} -#else -template (), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -auto or_else_impl(Exp &&exp, F &&f) -> Ret { - static_assert(detail::is_expected::value, "F must return an expected"); - return exp.has_value() ? std::forward(exp) - : detail::invoke(std::forward(f), - std::forward(exp).error()); -} - -template (), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -detail::decay_t or_else_impl(Exp &&exp, F &&f) { - return exp.has_value() ? std::forward(exp) - : (detail::invoke(std::forward(f), - std::forward(exp).error()), - std::forward(exp)); -} -#endif -} // namespace detail - -template -constexpr bool operator==(const expected &lhs, - const expected &rhs) { - return (lhs.has_value() != rhs.has_value()) - ? false - : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs); -} -template -constexpr bool operator!=(const expected &lhs, - const expected &rhs) { - return (lhs.has_value() != rhs.has_value()) - ? true - : (!lhs.has_value() ? lhs.error() != rhs.error() : *lhs != *rhs); -} -template -constexpr bool operator==(const expected &lhs, - const expected &rhs) { - return (lhs.has_value() != rhs.has_value()) - ? false - : (!lhs.has_value() ? lhs.error() == rhs.error() : true); -} -template -constexpr bool operator!=(const expected &lhs, - const expected &rhs) { - return (lhs.has_value() != rhs.has_value()) - ? true - : (!lhs.has_value() ? lhs.error() == rhs.error() : false); -} - -template -constexpr bool operator==(const expected &x, const U &v) { - return x.has_value() ? *x == v : false; -} -template -constexpr bool operator==(const U &v, const expected &x) { - return x.has_value() ? *x == v : false; -} -template -constexpr bool operator!=(const expected &x, const U &v) { - return x.has_value() ? *x != v : true; -} -template -constexpr bool operator!=(const U &v, const expected &x) { - return x.has_value() ? *x != v : true; -} - -template -constexpr bool operator==(const expected &x, const unexpected &e) { - return x.has_value() ? false : x.error() == e.value(); -} -template -constexpr bool operator==(const unexpected &e, const expected &x) { - return x.has_value() ? false : x.error() == e.value(); -} -template -constexpr bool operator!=(const expected &x, const unexpected &e) { - return x.has_value() ? true : x.error() != e.value(); -} -template -constexpr bool operator!=(const unexpected &e, const expected &x) { - return x.has_value() ? true : x.error() != e.value(); -} - -template ::value || - std::is_move_constructible::value) && - detail::is_swappable::value && - std::is_move_constructible::value && - detail::is_swappable::value> * = nullptr> -void swap(expected &lhs, - expected &rhs) noexcept(noexcept(lhs.swap(rhs))) { - lhs.swap(rhs); -} -} // namespace tl