Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic arguments storage. Implementation of enhancement from issue #1170. #1584

Merged
merged 19 commits into from
Mar 16, 2020

Conversation

vsolontsov-ll
Copy link
Contributor

I agree that my contributions are licensed under the {fmt} license, and agree to future changes to the licensing.

(Hope I didn't miss any guidelines.)

@vsolontsov-ll
Copy link
Contributor Author

Travis builds are fixed. Let's give a try with appveyor (don't know how to configure it for my fork yet)

@vsolontsov-ll vsolontsov-ll reopened this Mar 10, 2020
Copy link
Contributor

@vitaut vitaut left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Please address the initial comments (inline).

include/fmt/core.h Outdated Show resolved Hide resolved
include/fmt/core.h Outdated Show resolved Hide resolved
include/fmt/core.h Outdated Show resolved Hide resolved
include/fmt/dyn-args.h Outdated Show resolved Hide resolved
include/fmt/dyn-args.h Outdated Show resolved Hide resolved
vsolontsov-ll and others added 9 commits March 12, 2020 09:34
named_args will be added in a separate PR.
Pending: avoid Args types from dynamic_format_arg_store template
parameters, replace std::forward_list<std::variant> with a custom list.
Pending: cleanup.
Removed constexpr
Pending -- move dyn-args.h to core.h
@vsolontsov-ll
Copy link
Contributor Author

Maybe worth adding

  1. dynamic_format_arg_store::reserve(size_t)
  2. static_assert into void push_back(std::reference_wrapper<T> arg) to prevent passing primitive types by ref

But I'd leave those for the a PR with named args.

Copy link
Contributor

@vitaut vitaut left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great. I left a few more comments, mostly minor/nits.

Comment on lines 1640 to 1650
#ifdef FMT_USE_STRING_VIEW
template <typename Traits, typename Char>
struct is_string_view<std::basic_string_view<Char, Traits>, Char>
: std::true_type {};
#endif

#ifdef FMT_USE_EXPERIMENTAL_STRING_VIEW
template <typename Traits, typename Char>
struct is_string_view<std::experimental::basic_string_view<Char, Traits>, Char>
: std::true_type {};
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be replaced with

template <typename Char>
struct is_string_view<std_string_view<Char>, Char> : std::true_type {};

I wouldn't bother with traits at this time since they are not handled elsewhere in {fmt}.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I'd create an issue to track it then...
There's a constructor for internal::basic_string_view from std::basic_string<> with all the template parameters. But std_string_view is recognized with only defaults...

template <typename T> struct is_ref_wrapper : std::false_type {};

template <typename T>
struct is_ref_wrapper<std::reference_wrapper<T>> : std::true_type {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to handle std::reference_wrapper?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, for me it's the main use-case :- ).
It's to let one to avoid dynamic memory allocation when the argument lifetime is guaranteed.

Actually I would want to make the dynamic_format_arg_store reusable and more friendly for those who concerned about memory allocations:

  1. add reserve(); -- reserves
  2. add clean(); -- drops the storage_ and resizes the data_ to zero, but doesn't de-allocate the storage_.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Thanks for clarification.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also let's rename this trait to is_reference_wrapper for consistency with the class name itself.

struct is_ref_wrapper<std::reference_wrapper<T>> : std::true_type {};

template <typename T, typename Context> struct need_dyn_copy {
using mapped_type = mapped_type_constant<T, Context>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you only use mapped_type::value I suggest replacing this with

static internal::type mapped_type = mapped_type_constant<T, Context>::value;

and using mapped_type in place of mapped_type::value.

Comment on lines 1670 to 1671
template <typename T, typename Context>
using need_dyn_copy_t = typename need_dyn_copy<T, Context>::type;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's kill this alias since need_dyn_copy is only used in one place.

class dyn_arg_storage {
// Workaround clang's -Wweak-vtables. For templates (unlike regular classes
// doesn't complain about inability to deduce translation unit to place vtable
// So dyn_arg_node_base is made a fake template
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean storage_node_base instead of dyn_arg_node_base?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, thx

template <typename T, typename Context>
using need_dyn_copy_t = typename need_dyn_copy<T, Context>::type;

class dyn_arg_storage {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd merge this class into dynamic_format_arg_store unless there is a strong reason to have it separate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 1733 to 1734
using string_type = std::basic_string<char_type>;
using value_type = basic_format_arg<Context>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd kill these two aliases since they are only used once each (and the notion of value_type is somewhat unclear here).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 1740 to 1741
// Storage of basic_format_arg must be contiguous
// Required by basic_format_args::args_ which is just a pointer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: missing punctuation at the end of sentenses (.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 1755 to 1767
template <typename T> const T& stored_value(const T& arg, std::false_type) {
return arg;
}

template <typename T>
const T& stored_value(const std::reference_wrapper<T>& arg, std::false_type) {
return arg.get();
}

template <typename T>
const stored_type<T>& stored_value(const T& arg, std::true_type) {
return storage_.push<stored_type<T>>(arg);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest replacing tag dispatch with a normal if in push_back.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

static_assert(
!std::is_base_of<internal::named_arg_base<char_type>, T>::value,
"Named arguments are not supported yet");
emplace_arg(stored_value(arg, internal::need_dyn_copy_t<T, Context>{}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e. replacing this with something like

if (const_check(internal::need_dyn_copy_t<T, Context>()))
  emplace_arg(storage_.push<stored_type<T>>(arg));
else
  emplace_arg(arg);

You'll need to move const_check from fmt/format.h to fmt/core.h. In the future this if will become constexpr if.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@vitaut
Copy link
Contributor

vitaut commented Mar 14, 2020

Maybe worth adding

  1. dynamic_format_arg_store::reserve(size_t)
  2. static_assert into void push_back(std::reference_wrapper<T> arg) to prevent passing primitive types by ref

But I'd leave those for the a PR with named args.

Yes, let's leave these for a separate PR.

@vsolontsov-ll
Copy link
Contributor Author

sorry, seems like I missed a few comments

Changed dispatching from tag to explicit `if`.
However I would prefer tag-based dispatching if `if constexpr`, but the
later is not available in older standards.
@vsolontsov-ll
Copy link
Contributor Author

Ok, seems to be done. Sorry for that false-start.

Copy link
Contributor

@vitaut vitaut left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more comments (and there are several left over from previous review).

template <typename T> struct is_ref_wrapper : std::false_type {};

template <typename T>
struct is_ref_wrapper<std::reference_wrapper<T>> : std::true_type {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also let's rename this trait to is_reference_wrapper for consistency with the class name itself.

Comment on lines 1667 to 1668
// Workaround clang's -Wweak-vtables. For templates (unlike regular classes
// doesn't complain about inability to deduce translation unit to place vtable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had problems parsing the above. How about rephrasing to something like:

  // Workaround clang's -Wweak-vtables for templates (unlike regular classes)
  // don't complain about inability to deduce translation unit to place vtable.

Copy link
Contributor Author

@vsolontsov-ll vsolontsov-ll Mar 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix.. Not exactly like you wrote but somehow... Hope it's better now

Comment on lines 1803 to 1804
Note that primitive type values are copied into basic_format_arg<>.
\endrst
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should just disallow passing reference wrappers of built-in types to prevent confusion.

Comment on lines +20 to +21
// Unfortunately the tests are compiled with old ABI
// So strings use COW.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a problem?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imagine a std::string with COW in shared state passed into dyn_arg_store by ref.
It takes fmt::string_view of the string. Next non-const subscription operator leads to COW, so string_view keeps pointing to an old version. So the test below won't work with old ABI std::string.

@vitaut vitaut merged commit 6012dc9 into fmtlib:master Mar 16, 2020
@vitaut
Copy link
Contributor

vitaut commented Mar 16, 2020

Merged, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants