Search code examples
c++templatesconstraintsc++20c++-concepts

Why can the type constraint `std::convertible_to` be used with only one template argument?


I've scrolled and searched through the standard and cppreference for hours to no avail, would really appreciate if someone could explain this occurance for me:

I am looking at the standard concept std::convertibe_to. Here's a simple example that I do understand

class A {};
class B : public A {};

std::convertible_to<A, B>; // false
std::convertible_to<B, A>; // true

Works as expected.

Now there is also another possible way to use it, that I don't quite understand

void foo(std::convertible_to<A> auto x) { /* ... */ }

, and this function can easily accept any type convertible to A. This is weird though, because the first template parameter ("From") is essencially dropped, and deduced on function call. This following function would also work, and I'm fairly certain it's actually equivalent to the previous one

template<typename T, std::convertible_to<T> S>
void foo(S x) { /* ... */ }

again the type of x is deduced when we call foo.

This works, despite the template requiring two parameters. I tried also with std::derived_from and it seems to work. This form of specifying a concept with only one template parameter even appears in the standard itself, so there must be some piece of syntax that explains it.

Notice that the only version of std::convertible_to that exists is in fact one that takes two template parameters.

Could anyone clarify why this works?


Solution

  • void foo( constraint<P0, P1, P2> auto x );
    

    this translates roughly to

    template<contraint<P0, P1, P2> X>
    void foo( X x );
    

    which translates roughly to

    template<class X> requires constraint<X, P0, P1, P2>
    void foo( X x );
    

    notice how the type X is prepended to the template arguments of the constraint.

    So in your case,

    template<typename T, std::convertible_to<T> S>
    void foo(S x) { /* ... */ }
    

    is roughly

    template<typename T, class S>
    requires std::convertible_to<S, T>
    void foo(S x) { /* ... */ }
    

    (I say roughly, because I believe they are not exactly equivalent in subtle ways. For example, the second one introduces the name X, while the first does not. And there are probably other differences of similar scale; what I mean is that understanding the translation will give you an understanding of what is translated. This is unlike for(:) loop-for(;;) loop correspondence; the standard specifies for(:) loops in terms of for(;;) loops, which isn't what I'm claiming above.)