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;
};
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;
}