Home > Software design >  Concept for constraining parameter pack to string-like types or types convertible to string
Concept for constraining parameter pack to string-like types or types convertible to string

Time:11-24

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

  1. it allows types that an std::string can be constructed from

  2. 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) {
    
     }
    
  3. 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::strings 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_stringable.

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)));
}
  • Related