Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to use C++20 concepts to do different things based on return type of a function?

I want to make a generic print(x) function, which behaves different for different types.

What I have so far works for all container types, including the one I wrote myself. However, either the "wrong" function is getting called or it won’t compile due to ambiguity.

Here is my code:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

#include <iostream>
#include <concepts>

class Container
{
    int x, y, z;
public:
    Container(int a, int b, int c) : x(a), y(b), z(c) {}
    Container() : x(0), y(0), z(0) {}
    std::size_t size() const { return 3; }
    const int& operator[] (std::size_t index) const { if(index == 0) return x; else if(index == 1) return y; else return z; }
    int& operator[] (std::size_t index) { if(index == 0) return x; else if(index == 1) return y; else return z; }
};

template<typename T>
concept printable_num = requires (T t, std::size_t s) 
{ 
    { t.size() } -> std::convertible_to<std::size_t>;
    { t[s] } -> std::same_as<int&>;
};
template<printable_num T>
void print(const T& t) {
    std::size_t i = 0;
    for(;i < t.size() - 1; i++)
        std::cout << t[i] << ", ";
    std::cout << t[i] << std::endl;
}
    

template<typename T>
concept printable = requires (T t, std::size_t s) 
{ 
    { t.size() } -> std::convertible_to<std::size_t>;
    { t[s] } -> std::convertible_to<char>;
};

template<printable T> 
void print(const T& t) {
    std::size_t i = 0;
    for(;i < t.size() - 1; i++)
        std::cout << t[i];
    std::cout << t[i] << std::endl;
}

int main()
{
    Container c{1, 2, 3};
    print(c);
    Container empty;
    print(empty);
    std::string s{"this is some string"};
    print(s);
    return 0;
}

As you can see, I want to print a separator if the type returned from operator[] is int&. This does not compile due to ambiguity.

Is there a way to make this compile and to get me where I want (call the print function without the separator for std::string and the one with separator for my own Container type)?

>Solution :

Given an integer (or lvalue to one), would you not agree that it is convertible to a char? The constraints check exactly what you have them check for the types in your question.

One way to tackle it would be by constraint subsumption. Meaning (in a very hand wavy fashion) that if your concepts are written as a conjugation (or disjunction) of the same basic constraints, a compiler can normalize the expression to choose the "more specialized one". Applying it to your example..

template<typename T>
concept printable = requires (T t, std::size_t s) 
{ 
    { t.size() } -> std::convertible_to<std::size_t>;
    { t[s] } -> std::convertible_to<char>;
};

template<typename T>
concept printable_num = printable<T> && requires (T t, std::size_t s) 
{ 
    { t[s] } -> std::same_as<int&>;
};

Note how we used printable to define printable_num as the "more specific" concept. Running this example, we get the output you are after.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading