I'm piecing together a C 20 puzzle. Here's what I want to do: Function append_params
will concatenate the url together with additional query parameters. To make design this in a dynamic and extensible way, I wanted to write a concept such that
it allows types that an std::string can be constructed from
it allows types convertible to string using std::to_string()
template<typename... Ts> requires requires(T a) { std::to_string(a); } auto append_params(std::pmr::string url, Ts... args) { }
it works for a pack of parameters
I've found useful information on point 2) here. However, for point 1) and 3) I'm rather clueless (I'm also new to concepts). How can I constrain the whole parameter pack (what's the syntax here?) and how can I make sure that from every parameter I can construct an std::string object?
Also, I would have to know at compile time if I want to use std::strings constructor or std::to_string to handle the case.
CodePudding user response:
When developing a concept, always start with the template code you want to constrain. You may adjust that code at some point, but you always want to start with that code (unconstrained).
So what you want is something like this:
template<typename... Ts>
auto append_params(std::string &url, Ts... &&args)
{
return (url ... std::forward<Ts>(args) );
}
This doesn't work if any of the types in args
are not std::string
s or something that can be concatenated with std::string
. But you want more. You want to take types which std::to_string
can be used on.
Note that this is a pretty bad interface, since std::to_string
cannot be extended by the user. They can make their types convertible to std::string
or some other concatenate-able string type like string_view
. But users cannot add overloads to std
library stuff, so they can't make a type to_string
able.
Regardless, to allow to_string
to work, we would need to change the code to concatenate with this expression: std::to_string(std::forward<Ts>(args))
. But that would only take types that to_string
works on, not types convertible to a string
.
So what we need is a new function which will use to_string
if that is appropriate or just return the original expression if it can be concatenated directly.
So we have two kinds of types: those that are inherently string-concatenatable, and those which are to_string
-able:
template<typename T>
concept is_direct_string_concatenatable = requires(std::string str, T t)
{
{str t} -> std::same_as<std::string>;
};
template<typename T>
concept is_to_stringable = requires(T t)
{
{std::to_string(t)} -> std::same_as<std::string>;
};
//Combines the two concepts.
template<typename T>
concept is_string_concatenable =
is_direct_string_concatenatable<T> ||
is_to_stringable <T>;
template<is_string_concatenable T>
decltype(auto) make_concatenable(T &&t)
{
//To_string gets priority.
if constexpr(is_to_stringable<T>)
return std::to_string(std::forward<T>(t));
else
return std::forward<T>(t);
}
So now your template function needs to use make_concatenable
and the concept:
template<is_string_concatenable ...Ts>
auto append_params(std::string url, Ts&& ...args)
{
return (url ... make_concatenable(std::forward<Ts>(args)));
}