Home > Enterprise >  Pass initializer lists and ranges seamlessly to a function parameter
Pass initializer lists and ranges seamlessly to a function parameter

Time:01-09

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.

CodePudding user response:

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

CodePudding user response:

You could use an array initializer like this : (same for a set_modes function)

#include <iostream>
#include <set>
#include <string>

class Toto
{
public:
    template<std::size_t N>
    Toto(const std::string(&modes)[N]) :
        m_modes{ std::begin(modes), std::end(modes) }
    {
    }

private:
    std::set<std::string> m_modes{};
};

int main()
{
    Toto toto{{"mode1", "mode2"}};

    return 0;
}
  • Related