0

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.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • How about overloading, so you have one function that takes a range, and another that takes an initializer list? – Some programmer dude Jan 08 '23 at 12:18
  • Oh and I really recommend you use the same pattern used by the standard library for "ranges": By using iterator pairs. That would increase the number of overloads to three. – Some programmer dude Jan 08 '23 at 12:20
  • > "How about overloading ?" I could, I just wanted to check if what I'm trying to achieve is possible (and also I think it's cleaner without overloads) >"I really recommend you [..] using iterator pairs." Isn't ranges the new way of passing parameters though ? There was a lot of complains about pair of iterators being too verbose for 99% of use cases – Timothée Chabat Jan 08 '23 at 12:30
  • I guess you tried `{ ... }` when it failed? Yeah, bare braces have no type to deduce from. It works for `std::initializer_list` because of dedicated clauses in the spec. I think you can cover all your bases with an additional `std::initializer_list` overload. That and your c'tor template should accept all ranges and inline initializer. – StoryTeller - Unslander Monica Jan 08 '23 at 12:37
  • Yeah it seems like I do need an overload .. one of you guys feel free to post an answer that I can resolve the thread with, or I'll just resolve myself ^^ – Timothée Chabat Jan 08 '23 at 12:44

2 Answers2

3

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

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
0

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;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19