c++templatesstatic-castexplicit-conversion

Different Behavior for const int and int in C++ template


When I use C++ template, I found that sometimes it gives a non-straightforward result.

#include <cstdlib>
#include <iostream>

template<class f0>
void func(const int& a1, f0& a2)
{
    std::cout << a1 + a2 << '\n';
    return;
}

template<class f0, class f1>
void func(f0& a1, f1& a2)
{
    std::cout << a1 - a2 << '\n';
    return;
}


int main(int argc, char *argv[])
{
    int bb = 3;
    int bbb = 3;
    func(static_cast<const int &>(bb), bbb); // 6
    func(bb, bbb); // 0
    func((int)bb, bbb); // 6
    
    func(static_cast<int &>(bb), bbb); // 0
    func(static_cast<int>(bb), bbb); // 6
    return 0;
}

When I pass an integer to first argument, It was recognized as a difference against const int &. However, when I re-pack it to int. I select the template of const int&.

I cannot find a good interpretation to explain the behavior. Does something have a suggestion for how it works?


Solution

  • TLDR;

    (int)bb is explicit type conversion and the result is an prvalue and a non-const lvalue reference such as int& cannot be bound to an prvalue.


    However, when I re-pack it to int. I select the template of const int&.

    Case 1

    Here we consider the call func((int)bb, bbb).

    Here the expression (int)bb is a prvalue of type int(and thus an rvalue) and since an rvalue cannot be bound to an lvalue reference to non-const type, the first overload void func(const int& a1, f0& a2) is the only viable option here.

    That is, the first parameter a1 of the second overload void func(f0& a1, f1& a2) is a non-const lvalue reference which cannot be bound to an rvalue and so the second overload is not viable. On the other hand, the first parameter a1 of the first overload void func(const int& a1, f0& a2) is a const lvalue reference which can be bound to an rvalue and thus this first overload is viable and will be chosen.

    This can be seen from expr.cast#1:

    The result of the expression (T) cast-expression is of type T. The result is an lvalue if T is an lvalue reference type or an rvalue reference to function type and an xvalue if T is an rvalue reference to object type; otherwise the result is a prvalue.

    (emphasis mine)


    Case 2

    Here we consider the call func(static_cast<int>(bb), bbb);

    Here also the static_cast<int>(bb) is a prvalue and thus the first overload is the only viable option.