Commit e3060212 authored by Dominik Charousset's avatar Dominik Charousset

Use string_view in config_option and config_value

parent 435cfae4
...@@ -18,9 +18,11 @@ ...@@ -18,9 +18,11 @@
#pragma once #pragma once
#include <memory>
#include <string> #include <string>
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/string_view.hpp"
namespace caf { namespace caf {
...@@ -49,8 +51,8 @@ public: ...@@ -49,8 +51,8 @@ public:
// -- constructors, destructors, and assignment operators -------------------- // -- constructors, destructors, and assignment operators --------------------
/// Constructs a config option. /// Constructs a config option.
config_option(std::string category, const char* name, std::string description, config_option(string_view category, string_view name, string_view description,
const meta_state* meta, void* storage = nullptr); const meta_state* meta, void* value = nullptr);
config_option(config_option&&) = default; config_option(config_option&&) = default;
...@@ -58,25 +60,20 @@ public: ...@@ -58,25 +60,20 @@ public:
// -- properties ------------------------------------------------------------- // -- properties -------------------------------------------------------------
inline const std::string& category() const noexcept { /// Returns the category of the option.
return category_; string_view category() const noexcept;
}
/// Returns the /// Returns the name of the option.
inline const std::string& long_name() const noexcept { string_view long_name() const noexcept;
return long_name_;
}
inline const std::string& short_names() const noexcept { /// Returns (optional) one-letter short names of the option.
return short_names_; string_view short_names() const noexcept;
}
inline const std::string& description() const noexcept { /// Returns a human-readable description of the option.
return description_; string_view description() const noexcept;
}
/// Returns the full name for this config option as "<category>.<long name>". /// Returns the full name for this config option as "<category>.<long name>".
std::string full_name() const; string_view full_name() const noexcept;
/// Checks whether `x` holds a legal value for this option. /// Checks whether `x` holds a legal value for this option.
error check(const config_value& x) const; error check(const config_value& x) const;
...@@ -86,7 +83,7 @@ public: ...@@ -86,7 +83,7 @@ public:
void store(const config_value& x) const; void store(const config_value& x) const;
/// Returns a human-readable representation of this option's expected type. /// Returns a human-readable representation of this option's expected type.
const std::string& type_name() const noexcept; string_view type_name() const noexcept;
/// Returns whether this config option stores a boolean flag. /// Returns whether this config option stores a boolean flag.
bool is_flag() const noexcept; bool is_flag() const noexcept;
...@@ -96,10 +93,13 @@ public: ...@@ -96,10 +93,13 @@ public:
optional<config_value> get() const; optional<config_value> get() const;
private: private:
std::string category_; string_view buf_slice(size_t from, size_t to) const noexcept;
std::string long_name_;
std::string short_names_; std::unique_ptr<char[]> buf_;
std::string description_; uint16_t category_separator_;
uint16_t long_name_separator_;
uint16_t short_names_separator_;
uint16_t buf_size_;
const meta_state* meta_; const meta_state* meta_;
mutable void* value_; mutable void* value_;
}; };
......
...@@ -30,36 +30,36 @@ class config_option_adder { ...@@ -30,36 +30,36 @@ class config_option_adder {
public: public:
// -- constructors, destructors, and assignment operators -------------------- // -- constructors, destructors, and assignment operators --------------------
config_option_adder(config_option_set& target, const char* category); config_option_adder(config_option_set& target, string_view category);
// -- properties ------------------------------------------------------------- // -- properties -------------------------------------------------------------
/// Adds a config option to the category that synchronizes with `ref`. /// Adds a config option to the category that synchronizes with `ref`.
template <class T> template <class T>
config_option_adder& add(T& ref, const char* name, const char* description) { config_option_adder& add(T& ref, string_view name, string_view description) {
return add_impl(make_config_option(ref, category_, name, description)); return add_impl(make_config_option(ref, category_, name, description));
} }
/// Adds a config option to the category. /// Adds a config option to the category.
template <class T> template <class T>
config_option_adder& add(const char* name, const char* description) { config_option_adder& add(string_view name, string_view description) {
return add_impl(make_config_option<T>(category_, name, description)); return add_impl(make_config_option<T>(category_, name, description));
} }
/// For backward compatibility only. Do not use for new code! /// For backward compatibility only. Do not use for new code!
/// @private /// @private
config_option_adder& add_neg(bool& ref, const char* name, config_option_adder& add_neg(bool& ref, string_view name,
const char* description); string_view description);
/// For backward compatibility only. Do not use for new code! /// For backward compatibility only. Do not use for new code!
/// @private /// @private
config_option_adder& add_us(size_t& ref, const char* name, config_option_adder& add_us(size_t& ref, string_view name,
const char* description); string_view description);
/// For backward compatibility only. Do not use for new code! /// For backward compatibility only. Do not use for new code!
/// @private /// @private
config_option_adder& add_ms(size_t& ref, const char* name, config_option_adder& add_ms(size_t& ref, string_view name,
const char* description); string_view description);
private: private:
// -- properties ------------------------------------------------------------- // -- properties -------------------------------------------------------------
...@@ -72,7 +72,7 @@ private: ...@@ -72,7 +72,7 @@ private:
config_option_set& xs_; config_option_set& xs_;
/// Category for all options generated by this adder. /// Category for all options generated by this adder.
const char* category_; string_view category_;
}; };
} // namespace caf } // namespace caf
...@@ -100,15 +100,15 @@ public: ...@@ -100,15 +100,15 @@ public:
/// Adds a config option to the set. /// Adds a config option to the set.
template <class T> template <class T>
config_option_set& add(const char* category, const char* name, config_option_set& add(string_view category, string_view name,
const char* description = "") { string_view description = "") {
return add(make_config_option<T>(category, name, description)); return add(make_config_option<T>(category, name, description));
} }
/// Adds a config option to the set that synchronizes its value with `ref`. /// Adds a config option to the set that synchronizes its value with `ref`.
template <class T> template <class T>
config_option_set& add(T& ref, const char* category, const char* name, config_option_set& add(T& ref, string_view category, string_view name,
const char* description = "") { string_view description = "") {
return add(make_config_option<T>(ref, category, name, description)); return add(make_config_option<T>(ref, category, name, description));
} }
......
...@@ -97,11 +97,11 @@ public: ...@@ -97,11 +97,11 @@ public:
// -- parsing ---------------------------------------------------------------- // -- parsing ----------------------------------------------------------------
/// Tries to parse a value from `str`. /// Tries to parse a value from `str`.
static expected<config_value> parse(std::string::const_iterator first, static expected<config_value> parse(string_view::iterator first,
std::string::const_iterator last); string_view::iterator last);
/// Tries to parse a value from `str`. /// Tries to parse a value from `str`.
static expected<config_value> parse(const std::string& str); static expected<config_value> parse(string_view str);
// -- properties ------------------------------------------------------------- // -- properties -------------------------------------------------------------
......
...@@ -120,6 +120,7 @@ class scheduled_actor; ...@@ -120,6 +120,7 @@ class scheduled_actor;
class scoped_actor; class scoped_actor;
class serializer; class serializer;
class stream_manager; class stream_manager;
class string_view;
class type_erased_tuple; class type_erased_tuple;
class type_erased_value; class type_erased_value;
class uniform_type_info_map; class uniform_type_info_map;
...@@ -143,12 +144,12 @@ struct prohibit_top_level_spawn_marker; ...@@ -143,12 +144,12 @@ struct prohibit_top_level_spawn_marker;
// -- free template functions -------------------------------------------------- // -- free template functions --------------------------------------------------
template <class T> template <class T>
config_option make_config_option(const char* category, const char* name, config_option make_config_option(string_view category, string_view name,
const char* description); string_view description);
template <class T> template <class T>
config_option make_config_option(T& storage, const char* category, config_option make_config_option(T& storage, string_view category,
const char* name, const char* description); string_view name, string_view description);
// -- enums -------------------------------------------------------------------- // -- enums --------------------------------------------------------------------
......
...@@ -54,15 +54,15 @@ config_option::meta_state option_meta_state<T>::instance{ ...@@ -54,15 +54,15 @@ config_option::meta_state option_meta_state<T>::instance{
/// Creates a config option that synchronizes with `storage`. /// Creates a config option that synchronizes with `storage`.
template <class T> template <class T>
config_option make_config_option(const char* category, const char* name, config_option make_config_option(string_view category, string_view name,
const char* description) { string_view description) {
return {category, name, description, &detail::option_meta_state<T>::instance}; return {category, name, description, &detail::option_meta_state<T>::instance};
} }
/// Creates a config option that synchronizes with `storage`. /// Creates a config option that synchronizes with `storage`.
template <class T> template <class T>
config_option make_config_option(T& storage, const char* category, config_option make_config_option(T& storage, string_view category,
const char* name, const char* description) { string_view name, string_view description) {
return {category, name, description, &detail::option_meta_state<T>::instance, return {category, name, description, &detail::option_meta_state<T>::instance,
std::addressof(storage)}; std::addressof(storage)};
} }
...@@ -70,32 +70,32 @@ config_option make_config_option(T& storage, const char* category, ...@@ -70,32 +70,32 @@ config_option make_config_option(T& storage, const char* category,
// -- backward compatbility, do not use for new code ! ------------------------- // -- backward compatbility, do not use for new code ! -------------------------
// Inverts the value when writing to `storage`. // Inverts the value when writing to `storage`.
config_option make_negated_config_option(bool& storage, const char* category, config_option make_negated_config_option(bool& storage, string_view category,
const char* name, string_view name,
const char* description); string_view description);
// Reads timespans, but stores an integer representing microsecond resolution. // Reads timespans, but stores an integer representing microsecond resolution.
config_option make_us_resolution_config_option(size_t& storage, config_option make_us_resolution_config_option(size_t& storage,
const char* category, string_view category,
const char* name, string_view name,
const char* description); string_view description);
// Reads timespans, but stores an integer representing millisecond resolution. // Reads timespans, but stores an integer representing millisecond resolution.
config_option make_ms_resolution_config_option(size_t& storage, config_option make_ms_resolution_config_option(size_t& storage,
const char* category, string_view category,
const char* name, string_view name,
const char* description); string_view description);
// -- specializations for common types ----------------------------------------- // -- specializations for common types -----------------------------------------
#define CAF_SPECIALIZE_MAKE_CONFIG_OPTION(type) \ #define CAF_SPECIALIZE_MAKE_CONFIG_OPTION(type) \
template <> \ template <> \
config_option make_config_option<std::string>( \ config_option make_config_option<std::string>( \
std::string & storage, const char* category, const char* name, \ std::string & storage, string_view category, string_view name, \
const char* description); \ string_view description); \
template <> \ template <> \
config_option make_config_option<std::string>( \ config_option make_config_option<std::string>( \
const char* category, const char* name, const char* description) string_view category, string_view name, string_view description)
CAF_SPECIALIZE_MAKE_CONFIG_OPTION(atom_value); CAF_SPECIALIZE_MAKE_CONFIG_OPTION(atom_value);
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <iosfwd>
#include <iterator> #include <iterator>
#include <limits> #include <limits>
#include <type_traits> #include <type_traits>
...@@ -286,3 +287,9 @@ private: ...@@ -286,3 +287,9 @@ private:
}; };
} // namespace caf } // namespace caf
namespace std {
std::ostream& operator<<(std::ostream& out, caf::string_view);
} // namespace std
...@@ -18,8 +18,9 @@ ...@@ -18,8 +18,9 @@
#include "caf/config_option.hpp" #include "caf/config_option.hpp"
#include <cstring> #include <algorithm>
#include <iostream> #include <limits>
#include <numeric>
#include "caf/config.hpp" #include "caf/config.hpp"
#include "caf/config_value.hpp" #include "caf/config_value.hpp"
...@@ -29,41 +30,73 @@ ...@@ -29,41 +30,73 @@
using std::move; using std::move;
using std::string; using std::string;
namespace { namespace caf {
// -- constructors, destructors, and assignment operators ----------------------
string get_long_name(const char* name) { config_option::config_option(string_view category, string_view name,
auto i= strchr(name, ','); string_view description, const meta_state* meta,
return i != nullptr ? string{name, i} : string{name}; void* value)
: meta_(meta),
value_(value) {
using std::copy;
using std::accumulate;
auto comma = name.find(',');
auto long_name = name.substr(0, comma);
auto short_names = comma == string_view::npos ? string_view{}
: name.substr(comma + 1);
auto total_size = [](std::initializer_list<string_view> xs) {
return (xs.size() - 1) // one separator between all fields
+ accumulate(xs.begin(), xs.end(), size_t{0},
[](size_t x, string_view sv) { return x + sv.size(); });
};
auto ts = total_size({category, long_name, short_names, description});
CAF_ASSERT(ts <= std::numeric_limits<uint16_t>::max());
buf_size_ = static_cast<uint16_t>(ts);
buf_.reset(new char[ts]);
// fille the buffer with "<category>.<long-name>,<short-name>,<descriptions>"
auto first = buf_.get();
auto i = first;
auto pos = [&] {
return static_cast<uint16_t>(std::distance(first, i));
};
// <category>.
i = copy(category.begin(), category.end(), i);
category_separator_ = pos();
*i++ = '.';
// <long-name>,
i = copy(long_name.begin(), long_name.end(), i);
long_name_separator_ = pos();
*i++ = ',';
// <short-names>,
i = copy(short_names.begin(), short_names.end(), i);
short_names_separator_ = pos();
*i++ = ',';
// <description>
copy(description.begin(), description.end(), i);
CAF_ASSERT(pos() == buf_size_);
} }
string get_short_names(const char* name) { // -- properties ---------------------------------------------------------------
auto substr = strchr(name, ',');
if (substr != nullptr) string_view config_option::category() const noexcept {
return ++substr; return buf_slice(0, category_separator_);
return string{};
} }
} // namespace <anonymous> string_view config_option::long_name() const noexcept {
return buf_slice(category_separator_ + 1, long_name_separator_);
}
namespace caf { string_view config_option::short_names() const noexcept {
return buf_slice(long_name_separator_ + 1, short_names_separator_);
}
config_option::config_option(string category, const char* name, string_view config_option::description() const noexcept {
string description, const meta_state* meta, return buf_slice(short_names_separator_ + 1, buf_size_);
void* value)
: category_(move(category)),
long_name_(get_long_name(name)),
short_names_(get_short_names(name)),
description_(move(description)),
meta_(meta),
value_(value) {
CAF_ASSERT(meta_ != nullptr);
} }
string config_option::full_name() const { string_view config_option::full_name() const noexcept {
std::string result = category(); return buf_slice(0, long_name_separator_);
result += '.';
result += long_name_;
return result;
} }
error config_option::check(const config_value& x) const { error config_option::check(const config_value& x) const {
...@@ -78,7 +111,7 @@ void config_option::store(const config_value& x) const { ...@@ -78,7 +111,7 @@ void config_option::store(const config_value& x) const {
} }
} }
const std::string& config_option::type_name() const noexcept { string_view config_option::type_name() const noexcept {
return meta_->type_name; return meta_->type_name;
} }
...@@ -92,4 +125,9 @@ optional<config_value> config_option::get() const { ...@@ -92,4 +125,9 @@ optional<config_value> config_option::get() const {
return none; return none;
} }
string_view config_option::buf_slice(size_t from, size_t to) const noexcept {
CAF_ASSERT(from <= to);
return {buf_.get() + from, to - from};
}
} // namespace caf } // namespace caf
...@@ -23,26 +23,26 @@ ...@@ -23,26 +23,26 @@
namespace caf { namespace caf {
config_option_adder::config_option_adder(config_option_set& target, config_option_adder::config_option_adder(config_option_set& target,
const char* category) string_view category)
: xs_(target), : xs_(target),
category_(category) { category_(category) {
// nop // nop
} }
config_option_adder& config_option_adder::add_neg(bool& ref, const char* name, config_option_adder& config_option_adder::add_neg(bool& ref, string_view name,
const char* description) { string_view description) {
return add_impl(make_negated_config_option(ref, category_, return add_impl(make_negated_config_option(ref, category_,
name, description)); name, description));
} }
config_option_adder& config_option_adder::add_us(size_t& ref, const char* name, config_option_adder& config_option_adder::add_us(size_t& ref, string_view name,
const char* description) { string_view description) {
return add_impl(make_us_resolution_config_option(ref, category_, return add_impl(make_us_resolution_config_option(ref, category_,
name, description)); name, description));
} }
config_option_adder& config_option_adder::add_ms(size_t& ref, const char* name, config_option_adder& config_option_adder::add_ms(size_t& ref, string_view name,
const char* description) { string_view description) {
return add_impl(make_ms_resolution_config_option(ref, category_, return add_impl(make_ms_resolution_config_option(ref, category_,
name, description)); name, description));
} }
......
...@@ -34,19 +34,13 @@ struct string_builder { ...@@ -34,19 +34,13 @@ struct string_builder {
std::string result; std::string result;
}; };
template <size_t N> string_builder& operator<<(string_builder& builder, char ch) {
string_builder& operator<<(string_builder& builder, const char (&cstr)[N]) { builder.result += ch;
builder.result.append(cstr, N - 1);
return builder; return builder;
} }
string_builder& operator<<(string_builder& builder, char c) { string_builder& operator<<(string_builder& builder, caf::string_view str) {
builder.result += c; builder.result.append(str.data(), str.size());
return builder;
}
string_builder& operator<<(string_builder& builder, const std::string& str) {
builder.result += str;
return builder; return builder;
} }
...@@ -94,8 +88,8 @@ std::string config_option_set::help_text(bool global_only) const { ...@@ -94,8 +88,8 @@ std::string config_option_set::help_text(bool global_only) const {
}; };
// Sort argument + description by category. // Sort argument + description by category.
using pair = std::pair<std::string, option_pointer>; using pair = std::pair<std::string, option_pointer>;
std::set<std::string> categories; std::set<string_view> categories;
std::multimap<std::string, pair> args; std::multimap<string_view, pair> args;
size_t max_arg_size = 0; size_t max_arg_size = 0;
for (auto& opt : opts_) { for (auto& opt : opts_) {
if (!global_only || opt.category() == "global") { if (!global_only || opt.category() == "global") {
...@@ -132,7 +126,7 @@ auto config_option_set::parse(config_map& config, argument_iterator first, ...@@ -132,7 +126,7 @@ auto config_option_set::parse(config_map& config, argument_iterator first,
auto consume = [&](const config_option& opt, iter arg_begin, iter arg_end) { auto consume = [&](const config_option& opt, iter arg_begin, iter arg_end) {
// Extract option name and category. // Extract option name and category.
auto opt_name = opt.long_name(); auto opt_name = opt.long_name();
string opt_ctg = opt.category(); auto opt_ctg = opt.category();
// Try inserting a new submap into the config or fill existing one. // Try inserting a new submap into the config or fill existing one.
auto& submap = config[opt_ctg]; auto& submap = config[opt_ctg];
// Flags only consume the current element. // Flags only consume the current element.
...@@ -145,7 +139,9 @@ auto config_option_set::parse(config_map& config, argument_iterator first, ...@@ -145,7 +139,9 @@ auto config_option_set::parse(config_map& config, argument_iterator first,
} else { } else {
if (arg_begin == arg_end) if (arg_begin == arg_end)
return pec::missing_argument; return pec::missing_argument;
auto val = config_value::parse(arg_begin, arg_end); auto slice_size = static_cast<size_t>(std::distance(arg_begin, arg_end));
string_view slice{&*arg_begin, slice_size};
auto val = config_value::parse(slice);
if (!val) if (!val)
return pec::illegal_argument; return pec::illegal_argument;
if (opt.check(*val) != none) if (opt.check(*val) != none)
......
...@@ -50,8 +50,8 @@ config_value::~config_value() { ...@@ -50,8 +50,8 @@ config_value::~config_value() {
// -- parsing ------------------------------------------------------------------ // -- parsing ------------------------------------------------------------------
expected<config_value> config_value::parse(std::string::const_iterator first, expected<config_value> config_value::parse(string_view::iterator first,
std::string::const_iterator last) { string_view::iterator last) {
using namespace detail; using namespace detail;
auto i = first; auto i = first;
// Sanity check. // Sanity check.
...@@ -62,7 +62,7 @@ expected<config_value> config_value::parse(std::string::const_iterator first, ...@@ -62,7 +62,7 @@ expected<config_value> config_value::parse(std::string::const_iterator first,
if (++i == last) if (++i == last)
return make_error(pec::unexpected_eof); return make_error(pec::unexpected_eof);
// Dispatch to parser. // Dispatch to parser.
parser::state<std::string::const_iterator> res; parser::state<string_view::iterator> res;
detail::ini_value_consumer f; detail::ini_value_consumer f;
res.i = i; res.i = i;
res.e = last; res.e = last;
...@@ -84,7 +84,7 @@ expected<config_value> config_value::parse(std::string::const_iterator first, ...@@ -84,7 +84,7 @@ expected<config_value> config_value::parse(std::string::const_iterator first,
} }
} }
expected<config_value> config_value::parse(const std::string& str) { expected<config_value> config_value::parse(string_view str) {
return parse(str.begin(), str.end()); return parse(str.begin(), str.end());
} }
......
...@@ -54,15 +54,15 @@ config_value get_impl(const void* ptr) { ...@@ -54,15 +54,15 @@ config_value get_impl(const void* ptr) {
#define DEFAULT_MAKE_IMPL(type) \ #define DEFAULT_MAKE_IMPL(type) \
template <> \ template <> \
config_option make_config_option<type>(type & storage, const char* category, \ config_option make_config_option<type>(type & storage, string_view category, \
const char* name, \ string_view name, \
const char* description) { \ string_view description) { \
return {category, name, description, &type##_meta, &storage}; \ return {category, name, description, &type##_meta, &storage}; \
} \ } \
template <> \ template <> \
config_option make_config_option<type>(const char* category, \ config_option make_config_option<type>(string_view category, \
const char* name, \ string_view name, \
const char* description) { \ string_view description) { \
return {category, name, description, &type##_meta, nullptr}; \ return {category, name, description, &type##_meta, nullptr}; \
} }
...@@ -136,23 +136,23 @@ DEFAULT_META(string); ...@@ -136,23 +136,23 @@ DEFAULT_META(string);
} // namespace anonymous } // namespace anonymous
config_option make_negated_config_option(bool& storage, const char* category, config_option make_negated_config_option(bool& storage, string_view category,
const char* name, string_view name,
const char* description) { string_view description) {
return {category, name, description, &bool_neg_meta, &storage}; return {category, name, description, &bool_neg_meta, &storage};
} }
config_option make_us_resolution_config_option(size_t& storage, config_option make_us_resolution_config_option(size_t& storage,
const char* category, string_view category,
const char* name, string_view name,
const char* description) { string_view description) {
return {category, name, description, &us_res_meta, &storage}; return {category, name, description, &us_res_meta, &storage};
} }
config_option make_ms_resolution_config_option(size_t& storage, config_option make_ms_resolution_config_option(size_t& storage,
const char* category, string_view category,
const char* name, string_view name,
const char* description) { string_view description) {
return {category, name, description, &ms_res_meta, &storage}; return {category, name, description, &ms_res_meta, &storage};
} }
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <cstring> #include <iostream>
#include <stdexcept> #include <stdexcept>
#include "caf/config.hpp" #include "caf/config.hpp"
...@@ -306,3 +306,13 @@ size_type string_view::find_last_not_of(const_pointer str, ...@@ -306,3 +306,13 @@ size_type string_view::find_last_not_of(const_pointer str,
} }
} // namespace caf } // namespace caf
namespace std {
std::ostream& operator<<(std::ostream& out, caf::string_view str) {
for (auto ch : str)
out.put(ch);
return out;
}
} // namespace std
...@@ -30,9 +30,9 @@ using std::string; ...@@ -30,9 +30,9 @@ using std::string;
namespace { namespace {
constexpr const char* category = "category"; constexpr string_view category = "category";
constexpr const char* name = "name"; constexpr string_view name = "name";
constexpr const char* explanation = "explanation"; constexpr string_view explanation = "explanation";
template<class T> template<class T>
constexpr int64_t overflow() { constexpr int64_t overflow() {
...@@ -45,7 +45,7 @@ constexpr int64_t underflow() { ...@@ -45,7 +45,7 @@ constexpr int64_t underflow() {
} }
template <class T> template <class T>
optional<T> read(const std::string& arg) { optional<T> read(string_view arg) {
auto co = make_config_option<T>(category, name, explanation); auto co = make_config_option<T>(category, name, explanation);
auto res = config_value::parse(arg); auto res = config_value::parse(arg);
if (res && holds_alternative<T>(*res)) { if (res && holds_alternative<T>(*res)) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment