I am currently trying to get used to C++20 especially concepts and ranges in this case. Hopefully the title fits my problem I am not really sure yet what I ran into.
I want to create a set method for my class AttribueMap
to pass any iterable input with the underlying type that is convertible to string
to my class. The class is holding a simple map< string, vector< string >>
.
The concept I am using is the following:
template< typename input_data_t, typename range_element_t >
concept is_range_with_elements_of_type = std::ranges::input_range< input_data_t >
&& std::convertible_to< std::ranges::range_value_t< input_data_t >, range_element_t >;
When I pass vector< string >
or array< string >
or even vector< const char* >
it works. But initializer_list< const char* >
or a static constexpr array< string_view >
doesn't work.
I get the feeling its about the rvalue types, so how can I set the constraints for the set method to work with this type of temporary input, too?
The boiled down code:
#include <ranges>
#include <concepts>
#include <string>
#include <vector>
#include <map>
#include <string_view>
template< typename input_data_t, typename range_element_t >
concept is_range_with_elements_of_type = std::ranges::input_range< input_data_t > && std::convertible_to< std::ranges::range_value_t< input_data_t >, range_element_t >;
class AttributeMap
{
public:
using attribute_name_t = std::string;
using attribute_element_t = std::string;
using attribute_data_t = std::vector< attribute_element_t >;
template< typename input_data_t >
requires is_range_with_elements_of_type< input_data_t, attribute_element_t >
bool setAttribute(const attribute_name_t&, const input_data_t&)
{
if constexpr (std::convertible_to< input_data_t, attribute_element_t>)
{
//currently not implemented
return false;
}
else if constexpr (is_range_with_elements_of_type< input_data_t, attribute_element_t >)
{
//currently not implemented
return false;
}
return false;
}
private:
std::map< attribute_name_t, attribute_data_t > m_data;
};
#include <array>
int main()
{
AttributeMap dut;
dut.setAttribute("Friends", { "Chery", "Jack", "Nguyen" }); //error
dut.setAttribute(std::string("other Friends"), std::array<std::string, 4>{ "Milo of Croton", "Cleopatra", "300", "..." });
dut.setAttribute(std::string("even more Friends"), std::vector<const char*>{ "Karl", "Gilgamesh" });
{
const std::string attribute{ "books" };
const std::vector< std::string > attributeData{ "Book1", "Book2" };
dut.setAttribute(attribute, attributeData);
}
{
const std::string attribute{ "Cloths" };
const std::array< std::string, 5 > attributeData{ "Shirt", "Pullover", "Jeans", "Cap", "Sneaker" };
dut.setAttribute(attribute, attributeData);
}
{
const std::string attribute{ "Comments" };
static constexpr std::array< std::string_view, 3 > attributeData{ "great", "rofl u said lol", " . " };
dut.setAttribute(attribute, attributeData); //error
}
}
The errors I get with msvc (14.36.32502) are:
Error C2672 'AttributeMap::setAttribute': no matching overloaded function found
for the lines 11:
dut.setAttribute("Friends", { "Chery", "Jack", "Nguyen" });
and the last
dut.setAttribute(attribute, attributeData);
in line 27, using static constexpr std::array< std::string_view, 3 >
.
But
initializer_list< const char* >
or astatic
constexpr array< string_view >
doesn't work.
{}
does not participate in template deduction, so the compiler does not know the type of { "Chery", "Jack", "Nguyen" }
. You can explicitly specify the type or add a default template parameter for setAttribute
template<typename input_data_t = std::initializer_list<attribute_element_t>>
requires is_range_with_elements_of_type< input_data_t, attribute_element_t>
bool setAttribute(const attribute_name_t&, const input_data_t&);
For array<string_view, N>
, the problem is that string_view
cannot be implicitly converted to string
, which means the following assertion fails
static_assert(std::convertible_to<std::string_view, std::string>); // failed
which makes the constraint not satisfied. One of the workarounds is to use constructible_from
instead of convertible_to
to compose is_range_with_elements_of_type
concept
template<typename input_data_t, typename range_element_t>
concept is_range_with_elements_of_type =
std::ranges::input_range<input_data_t> &&
std::constructible_from<range_element_t, std::ranges::range_value_t<input_data_t>>;
in which case you need to explicitly call the constructor to construct the string
.