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.