Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

range parameter for any iterable input with properly convertible elements

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 >.

like image 223
Tobxon Avatar asked Dec 17 '25 12:12

Tobxon


2 Answers

But initializer_list< const char* > or a static 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.

like image 168
康桓瑋 Avatar answered Dec 19 '25 00:12

康桓瑋


In

dut.setAttribute("Friends", { "Chery", "Jack", "Nguyen" });

you have a braced-init-list

{ "Chery", "Jack", "Nguyen" }

which can be used to list-initialize an object, but you've left it up to template argument deduction to figure out what type of object input_data_t is that should be initialized.

The compiler has a braced-init-list ready to initialize an object but no information about what type to initialize, so compilation fails.

You can either specify the type of object at the call site, like a std::initializer_list<const char*>:

dut.setAttribute("Friends", 
                 std::initializer_list<const char*>{"Chery", "Jack", "Nguyen"});

or add an overload that takes a std::initializer_list with elements convertible to attribute_element_t:

#include <initializer_list>

template<typename input_data_t>
requires is_range_with_elements_of_type<std::initializer_list<input_data_t>,
                                        attribute_element_t>
bool setAttribute(const attribute_name_t& an,
                  std::initializer_list<input_data_t> il)
{
    //...
}

From std::initializer_list:
A std::initializer_list object is automatically constructed when:

  • a braced-init-list is used to list-initialize an object, where the corresponding constructor accepts an std::initializer_list parameter
  • a braced-init-list is used as the right operand of assignment or as a function call argument, and the corresponding assignment operator/function accepts an std::initializer_list parameter
  • a braced-init-list is bound to auto, including in a ranged for loop
    Which means that this will magically work:
    auto il = {"Chery", "Jack", "Nguyen"};
    static_assert(std::is_same_v<decltype(il),
                  std::initializer_list<const char*>>);
    dut.setAttribute("Friends", il);
    
    ... but skipping binding to auto like in your code will not.
like image 39
Ted Lyngmo Avatar answered Dec 19 '25 02:12

Ted Lyngmo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!