c++c++-conceptsambiguous

Ambiguous call for template functions with concepts


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.


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{});

Wokring demo.


Solution

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