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

Time:02-14

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:

#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)?

CodePudding user response:

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.

  • Related