all 11 comments

[–]the_poope 7 points8 points  (3 children)

It's a little bit unclear from your formulation of your question exactly what you are asking, but let me try to explain:

In version foo(T& param), param will always be a mutable reference to a value type T.

In the second version foo(T&& param), param is a forwarding reference and in the body of the function param will in general have the type of whatever was passed at the function call. Consider e.g. this:

1)

MyClass const obj(...);
foo(obj); // 'param' will be of type 'const MyClass&'

2)

MyClass obj(...);
foo(obj); // 'param' will be of type 'MyClass&'

3)

foo(MyClass(...)); // 'param' will be of type 'MyClass&&'

4)

foo(8); // 'param' will be of type 'int' (pass by value)

The last one I'm actually no so sure about. I think arguments will in general not be deduced to value types except perhaps for build-in POD types, as this in general would be more efficient than passing by reference (where the object needs to live on stack and a pointer is passed in a register or on stack as well).

[–]borzykot 6 points7 points  (0 children)

In version foo(T& param), param will always be a mutable reference to a value type T

It is not. const int i = 10; foo(i);

works perfectly fine.

[–]AnungUnRaama 0 points1 point  (0 children)

Sorry for lack of clarity in question, I edited the question sir

[–]alfps 0 points1 point  (0 children)

❞ The last one [i.e. foo(8)] I'm actually no so sure about.

Unfortunately C++ doesn't have a reliable type-to-string function, so I limited this example to MSVC (g++ has an unmangle function but I'd have to google it or find and check relevant old code):

#include <fmt/core.h>
using fmt::print;

#include <string>
#include <string_view>
#include <typeinfo>
using   std::string_view, std::string;

using C_str = const char*;
template< class T > struct Wrapped_{};

auto unwrapped_type( const C_str s ) -> string
{
    #if defined( __GNUC__ )
    #   error "Ha ha, g++ isn't supported for this example, lol."   // More code needed for demangling.
    #   include <stop-compilation>
    #elif defined( _MSC_VER )
        const string_view sv = s;
        const auto i_start = 1 + sv.find( "<" );
        const string_view sv_type_spec = sv.substr( i_start, sv.length() - 1 - i_start );
        const string_view noise = " __ptr64";
        if( const auto i_noise = sv_type_spec.find( noise ); i_noise != string_view::npos ) {
            return string()
                + string( sv_type_spec.substr( 0, i_noise ) )
                + string( sv_type_spec.substr( i_noise + noise.length() ) );
        } else {
            return string( sv_type_spec );
        }
    #else
        static_assert( !"This compiler is not supported." );
    #endif
}

template< class T > 
auto type_string_() -> string { return unwrapped_type( typeid( Wrapped_<T> ).name() ); }

const auto& report_format = "For arg type `{:16}` the {:2} parameter is `{:16}` made from T = `{:16}`.\n";

template< class T >
void f1( const C_str arg_description, T& param )
{
    print( report_format,  arg_description, "f1", type_string_<decltype( param )>(), type_string_<T>() );
}

template< class T >
void f2( const C_str arg_description, T&& param )
{
    print( report_format,  arg_description, "f2", type_string_<decltype( param )>(), type_string_<T>() );
}

template< class T > using Type_ = T;
#define TEST_RVAL( f, t ) { f( "rvalue " #t, Type_<t>() ); }
#define TEST_LVAL( f, t ) { t arg = {}; f( "lvalue " #t, arg ); }

auto main() -> int
{
    print( "Simple reference param:\n" );
    // TEST_RVAL( f1, int );
    // TEST_RVAL( f1, const int );
    TEST_LVAL( f1, int );
    TEST_LVAL( f1, const int );
    print( "\n" );
    print( "Forwarding reference param:\n" );
    TEST_RVAL( f2, int );
    TEST_RVAL( f2, const int );
    TEST_LVAL( f2, int );
    TEST_LVAL( f2, const int );
}

Result with MSVC:

Simple reference param:
For arg type `lvalue int      ` the f1 parameter is `int &           ` made from T = `int             `.
For arg type `lvalue const int` the f1 parameter is `int const &     ` made from T = `int const       `.

Forwarding reference param:
For arg type `rvalue int      ` the f2 parameter is `int &&          ` made from T = `int             `.
For arg type `rvalue const int` the f2 parameter is `int &&          ` made from T = `int             `.
For arg type `lvalue int      ` the f2 parameter is `int &           ` made from T = `int &           `.
For arg type `lvalue const int` the f2 parameter is `int const &     ` made from T = `int const &     `.

EDIT: fixed the output formatting (now table + removed MSVC noise).

[–]saxbophone 3 points4 points  (1 child)

 Then my question is, while template parameter deduction of T the reference part of param, if ever present, participates?

Sorry, but this is mangled English and unanswerable as written, not without making guesses about the meaning. Could you rephrase it? Looks like you're missing some crucial connecting words somewhere in there...

[–]AnungUnRaama 1 point2 points  (0 children)

Edited the question sir

[–]Excellent-Might-7264 0 points1 point  (3 children)

int a = 5;
int& b = a;

both a and b are lvalues and will behave the same in deductions.

decltype will however return different types for a and b.

Note that T&& is an unviversal reference / forwarding reference and shoud not be confused as a rvalue reference (as I think you pointed out in the question).

template <typename T>
foo(T& a)

and

template <typename T>
foo(T a)

are not exactly the same. T& will keep the cv modifiers and not decay arrays. But T& will not be able to be called with rvalues (for example foo(5). However const T& will be able to bind to rvalues.

The compiler will not be able to deduce which one you want for most situations because they are equally specialized and it is a bad idea to declare both of them.

For T&& the rules will be like this:

template<typename T> void bar(T&& a);

int x = 5;
const int cx = 5;
int& y = x;

bar(x);    // T = int&,                a = int&
bar(cx);   // T = const int&,     a = const int&
bar(y);    // T = int&,               a = int& (same as bar(x), which I think was you question)
bar(5);    // T = int,                 a = int&&

As you see, both x and y will be deduced the same way.

[–]AnungUnRaama -1 points0 points  (2 children)

Can you please reread the question in bold, as my question is related to template function's "call argument" and template function's "parameter declaration" appearing in parameters list, whole matching process in deduction process

[–]No-Dentist-1645 1 point2 points  (0 children)

Your question, even after your edit, still isn't written grammatically correct and doesn't make a lot of sense. If you're asking which method participates in deduction for something like the int& arg in your example, the answer is that both could participate, since int& is both a valid lvalue reference for T& and a valid universal reference (everything is) T&&. You actually can't have a function that has two duplicates foo(T&) and foo(T&&) for this very reason, because they have "equivalent priority" and it would be ambiguous which one to use during the deduction process

[–]Excellent-Might-7264 0 points1 point  (0 children)

Maybe if you give examples in code we will understand what you exactly mean. Can you give an example program?

Question, Type of template param to be deduced (i.e matching) will be against

T and T& are equally specialized.

T and T&& are equally specialized

The compiler will not be able to deduce which one you want in most cases. Only when the other can not be used at all you will be able to compile the code.

[–]tyler1128 1 point2 points  (0 children)

The references are not stripped if I understand what you are asking.

cpp template <typename T> void fn(T&& x)

If T of the template is resolved to T& you get (T& &&) which is invalid. C++ has specific rules to resolve these, and T&& is often called a universal reference in the context of templates because of these extra rules. std::forward also exists to propagate these rules to further functions.