With this code below:
#define P std::cout << __PRETTY_FUNCTION__ << '\n'
template <typename ... arg_pack> concept is_tuple = requires(std::tuple<arg_pack ...>) { true; };
template <typename ... arg_pack> concept is_variant = requires(std::variant<arg_pack ...>) { true; };
template <is_tuple type_t> void X(const type_t &t) { P; } // A
//template <is_variant type_t> void X(const type_t &v) { P; } // B
void X(int) { P; }
int main()
{
std::tuple<int> t;
std::variant<int> v;
X(t);
X(v);
X(0);
return 0;
}
I'm getting this output:
void X(const type_t &) [type_t = std::tuple<int>] void X(const type_t &) [type_t = std::variant<int>] void X(int)
Uncommenting the line marked with B
comment, the compiler reports ambiguity:
error: call to 'X' is ambiguous
X(t);
^
note: candidate function [with type_t = std::tuple<int>]
template <is_tuple type_t> void X(const type_t &t) { P; }
^
note: candidate function [with type_t = std::tuple<int>]
template <is_variant type_t> void X(const type_t &v) { P; }
^
error: call to 'X' is ambiguous
X(v);
^
note: candidate function [with type_t = std::variant<int>]
template <is_tuple type_t> void X(const type_t &t) { P; }
^
note: candidate function [with type_t = std::variant<int>]
template <is_variant type_t> void X(const type_t &v) { P; }
^
2 errors generated.
This is very confusing.
X
with B
line commented doesn't report a compiler error but calls the incorrect function?X
with B
line uncommented is abiguous considering that std::is_same_v<std::tuple<int>, std::variant<int>>
is false?Thanks Jan Schultke and SO community, I've managed to workaround a simple is_tuple
and is_variant
with your help:
template <typename type_t>
concept is_tuple = []<typename ... arg_pack>(std::tuple<arg_pack ...>){ return true; }(type_t{});
template <typename type_t>
concept is_variant = []<typename ... arg_pack>(std::variant<arg_pack ...>){ return true; }(type_t{});
The call is ambiguous because you're misusing concepts, and both your concepts are effectively concept C = true;
In the concept:
template <typename ... arg_pack> concept is_tuple = requires(std::tuple<arg_pack ...>) { true; };
... the std::tuple<arg_pack>
isn't verifying that you somehow have a std::tuple
; the std::tuple<arg_pac>
is like an unused function parameter in the requires expression.
The type constraint <is_tuple type_t>
requires is_tuple<type_t>
, which expands to:
requires (std::tuple<my_type>) { true; } // always true
Ideally, you should create a concept for a tuple-like type, or a variant-like type. This is what the C++ standard library commonly does, and such concepts exists in the C++ standard, although they are exposition-only. See also: C++20 Concept to check tuple-like types
With a tuple-like concept, you will be much more flexible, and you can accept types like std::array
, std::pair
, etc. instead of just std::tuple
.