Search code examples
c++c++20initializer-listc++-conceptsstd-ranges

Pass initializer lists and ranges seamlessly to a function parameter


I am trying to create a function that will initialize an internal std::set<std::string>, and I want to expose an API that allows any type of ranges as input, including initializer lists. So what I'm aiming for is :

// API definition 

class Toto
{
public:
  void set_modes(??? modes_) {
    this->modes = {modes_.begin(), modes_.end()};
  }
private:
  std::set<std::string> modes;
};

// Expected API usage

Toto instance;
instance.set_modes({"super", "ultra"});

const std::vector<std::string> other_modes {"top", "notch"};
instance.set_modes(other_modes);

What I've tried is :

template<std::ranges::input_range Range>
void set_modes(Range&& modes_)
{
  this->modes = {modes_.begin(), modes_.end()};
}

and it works fine with an instantiated container, but it fails with a temporary initializer list. The error is the following (Clang 16.0) :

error: no matching member function for call to 'set_modes'

note: candidate template ignored: couldn't infer template argument 'Range'
  set_modes(Range&& modes)
  ^

My guess is that I somehow I have to tell my function that I only want ranges of std::string_view, but I'm not sure how to do that. Or am I asking for something impossible to do in the first place ? Thanks

[EDIT]

According to this answer C++ class initializable from any sequence type initializer lists are always a a special case so I guess I need an overload. Answer is 5 years old though, so except if someone tells me that there is some c++20 / c++23 witchcraft that can handle that in a single function, I'll mark this thread as resolved.


Solution

  • My guess is that I somehow I have to tell my function that I only want ranges of std::string_view, but I'm not sure how to do that.

    If you want to support {"super", "ultra"}, you can default the template parameter Range to initializer_list<string_view>. And for other ranges, you can constrain its value type to be convertible to string_view, which can be obtained through range_value_t.

    Also, you may need to constrain Range to common_range, since std::set's iterator-pair constructor expects both iterators to be of the same type:

    class Toto
    {
    public:
      template<std::ranges::input_range Range = 
               std::initializer_list<std::string_view>>
        requires std::ranges::common_range<Range> &&
                 std::convertible_to<std::ranges::range_value_t<Range>, 
                                     std::string_view>
      void set_modes(Range&& modes_) {
        this->modes = {modes_.begin(), modes_.end()};
      }
    private:
      std::set<std::string> modes;
    };
    

    Demo